refactor(api): scope oauth operations by integration
This commit is contained in:
@@ -46,7 +46,7 @@ const login = Effect.fn("cli.console.login.run")(function* (timeline: TimelineHo
|
|||||||
|
|
||||||
yield* request(() => timeline.pending("Starting authorization..."))
|
yield* request(() => timeline.pending("Starting authorization..."))
|
||||||
const started = yield* request((signal) =>
|
const started = yield* request((signal) =>
|
||||||
client.integration.connect.oauth(
|
client.integration.oauth.connect(
|
||||||
{
|
{
|
||||||
integrationID,
|
integrationID,
|
||||||
methodID: method.id,
|
methodID: method.id,
|
||||||
@@ -59,8 +59,8 @@ const login = Effect.fn("cli.console.login.run")(function* (timeline: TimelineHo
|
|||||||
const attempt = started.data
|
const attempt = started.data
|
||||||
yield* Effect.addFinalizer(() =>
|
yield* Effect.addFinalizer(() =>
|
||||||
request(() =>
|
request(() =>
|
||||||
client.integration.attempt.cancel(
|
client.integration.oauth.cancel(
|
||||||
{ attemptID: attempt.attemptID, location },
|
{ integrationID, attemptID: attempt.attemptID, location },
|
||||||
{ signal: AbortSignal.timeout(5_000) },
|
{ signal: AbortSignal.timeout(5_000) },
|
||||||
),
|
),
|
||||||
).pipe(Effect.ignore),
|
).pipe(Effect.ignore),
|
||||||
@@ -75,7 +75,7 @@ const login = Effect.fn("cli.console.login.run")(function* (timeline: TimelineHo
|
|||||||
}).pipe(Effect.ignore)
|
}).pipe(Effect.ignore)
|
||||||
yield* request(() => timeline.pending("Waiting for authorization..."))
|
yield* request(() => timeline.pending("Waiting for authorization..."))
|
||||||
|
|
||||||
const status = yield* waitForConsoleLogin(client, attempt.attemptID)
|
const status = yield* waitForConsoleLogin(client, integrationID, attempt.attemptID)
|
||||||
if (status.status === "failed") yield* Effect.fail(new Error(status.message))
|
if (status.status === "failed") yield* Effect.fail(new Error(status.message))
|
||||||
if (status.status === "expired") yield* Effect.fail(new Error("Device code expired"))
|
if (status.status === "expired") yield* Effect.fail(new Error("Device code expired"))
|
||||||
|
|
||||||
@@ -85,11 +85,12 @@ const login = Effect.fn("cli.console.login.run")(function* (timeline: TimelineHo
|
|||||||
|
|
||||||
const waitForConsoleLogin = Effect.fn("cli.console.login.wait")(function* (
|
const waitForConsoleLogin = Effect.fn("cli.console.login.wait")(function* (
|
||||||
client: OpenCodeClient,
|
client: OpenCodeClient,
|
||||||
|
integrationID: string,
|
||||||
attemptID: string,
|
attemptID: string,
|
||||||
) {
|
) {
|
||||||
while (true) {
|
while (true) {
|
||||||
const response = yield* request((signal) =>
|
const response = yield* request((signal) =>
|
||||||
client.integration.attempt.status({ attemptID, location }, { signal }),
|
client.integration.oauth.status({ integrationID, attemptID, location }, { signal }),
|
||||||
)
|
)
|
||||||
if (response.data.status !== "pending") return response.data
|
if (response.data.status !== "pending") return response.data
|
||||||
yield* Effect.sleep(500)
|
yield* Effect.sleep(500)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export default Runtime.handler(
|
|||||||
return yield* Effect.fail(new Error(`MCP server "${input.name}" is not an OAuth-capable remote server`))
|
return yield* Effect.fail(new Error(`MCP server "${input.name}" is not an OAuth-capable remote server`))
|
||||||
|
|
||||||
const started = yield* Effect.promise(() =>
|
const started = yield* Effect.promise(() =>
|
||||||
client.integration.connect.oauth({ integrationID: integration.id, methodID: method.id, inputs: {}, location }),
|
client.integration.oauth.connect({ integrationID: integration.id, methodID: method.id, inputs: {}, location }),
|
||||||
)
|
)
|
||||||
const attempt = started.data
|
const attempt = started.data
|
||||||
if (attempt.mode === "code")
|
if (attempt.mode === "code")
|
||||||
@@ -40,7 +40,7 @@ export default Runtime.handler(
|
|||||||
|
|
||||||
process.stdout.write(attempt.instructions + EOL + attempt.url + EOL)
|
process.stdout.write(attempt.instructions + EOL + attempt.url + EOL)
|
||||||
|
|
||||||
const result = yield* poll(client, attempt.attemptID)
|
const result = yield* poll(client, integration.id, attempt.attemptID)
|
||||||
if (result.status === "complete") {
|
if (result.status === "complete") {
|
||||||
process.stdout.write(`Authenticated with ${input.name}` + EOL)
|
process.stdout.write(`Authenticated with ${input.name}` + EOL)
|
||||||
return
|
return
|
||||||
@@ -52,15 +52,16 @@ export default Runtime.handler(
|
|||||||
|
|
||||||
const poll = (
|
const poll = (
|
||||||
client: OpenCodeClient,
|
client: OpenCodeClient,
|
||||||
|
integrationID: string,
|
||||||
attemptID: string,
|
attemptID: string,
|
||||||
): Effect.Effect<Exclude<IntegrationAttemptStatus, { status: "pending" }>> =>
|
): Effect.Effect<Exclude<IntegrationAttemptStatus, { status: "pending" }>> =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const status = yield* Effect.promise(() => client.integration.attempt.status({ attemptID, location })).pipe(
|
const status = yield* Effect.promise(() =>
|
||||||
Effect.map((result) => result.data),
|
client.integration.oauth.status({ integrationID, attemptID, location }),
|
||||||
)
|
).pipe(Effect.map((result) => result.data))
|
||||||
if (status.status === "pending") {
|
if (status.status === "pending") {
|
||||||
yield* Effect.sleep("1 second")
|
yield* Effect.sleep("1 second")
|
||||||
return yield* poll(client, attemptID)
|
return yield* poll(client, integrationID, attemptID)
|
||||||
}
|
}
|
||||||
return status
|
return status
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -419,7 +419,7 @@ export type IntegrationConnectKeyOperation<E = never> = (
|
|||||||
input: Endpoint10_2Input,
|
input: Endpoint10_2Input,
|
||||||
) => Effect.Effect<Endpoint10_2Output, E>
|
) => Effect.Effect<Endpoint10_2Output, E>
|
||||||
|
|
||||||
type Endpoint10_3Request = Parameters<RawClient["server.integration"]["integration.connect.oauth"]>[0]
|
type Endpoint10_3Request = Parameters<RawClient["server.integration"]["integration.oauth.connect"]>[0]
|
||||||
export type Endpoint10_3Input = {
|
export type Endpoint10_3Input = {
|
||||||
readonly integrationID: Endpoint10_3Request["params"]["integrationID"]
|
readonly integrationID: Endpoint10_3Request["params"]["integrationID"]
|
||||||
readonly location?: Endpoint10_3Request["query"]["location"]
|
readonly location?: Endpoint10_3Request["query"]["location"]
|
||||||
@@ -427,55 +427,54 @@ export type Endpoint10_3Input = {
|
|||||||
readonly inputs: Endpoint10_3Request["payload"]["inputs"]
|
readonly inputs: Endpoint10_3Request["payload"]["inputs"]
|
||||||
readonly label?: Endpoint10_3Request["payload"]["label"]
|
readonly label?: Endpoint10_3Request["payload"]["label"]
|
||||||
}
|
}
|
||||||
export type Endpoint10_3Output = EffectValue<ReturnType<RawClient["server.integration"]["integration.connect.oauth"]>>
|
export type Endpoint10_3Output = EffectValue<ReturnType<RawClient["server.integration"]["integration.oauth.connect"]>>
|
||||||
export type IntegrationConnectOauthOperation<E = never> = (
|
export type IntegrationOauthConnectOperation<E = never> = (
|
||||||
input: Endpoint10_3Input,
|
input: Endpoint10_3Input,
|
||||||
) => Effect.Effect<Endpoint10_3Output, E>
|
) => Effect.Effect<Endpoint10_3Output, E>
|
||||||
|
|
||||||
type Endpoint10_4Request = Parameters<RawClient["server.integration"]["integration.attempt.status"]>[0]
|
type Endpoint10_4Request = Parameters<RawClient["server.integration"]["integration.oauth.status"]>[0]
|
||||||
export type Endpoint10_4Input = {
|
export type Endpoint10_4Input = {
|
||||||
|
readonly integrationID: Endpoint10_4Request["params"]["integrationID"]
|
||||||
readonly attemptID: Endpoint10_4Request["params"]["attemptID"]
|
readonly attemptID: Endpoint10_4Request["params"]["attemptID"]
|
||||||
readonly location?: Endpoint10_4Request["query"]["location"]
|
readonly location?: Endpoint10_4Request["query"]["location"]
|
||||||
}
|
}
|
||||||
export type Endpoint10_4Output = EffectValue<ReturnType<RawClient["server.integration"]["integration.attempt.status"]>>
|
export type Endpoint10_4Output = EffectValue<ReturnType<RawClient["server.integration"]["integration.oauth.status"]>>
|
||||||
export type IntegrationAttemptStatusOperation<E = never> = (
|
export type IntegrationOauthStatusOperation<E = never> = (
|
||||||
input: Endpoint10_4Input,
|
input: Endpoint10_4Input,
|
||||||
) => Effect.Effect<Endpoint10_4Output, E>
|
) => Effect.Effect<Endpoint10_4Output, E>
|
||||||
|
|
||||||
type Endpoint10_5Request = Parameters<RawClient["server.integration"]["integration.attempt.complete"]>[0]
|
type Endpoint10_5Request = Parameters<RawClient["server.integration"]["integration.oauth.complete"]>[0]
|
||||||
export type Endpoint10_5Input = {
|
export type Endpoint10_5Input = {
|
||||||
|
readonly integrationID: Endpoint10_5Request["params"]["integrationID"]
|
||||||
readonly attemptID: Endpoint10_5Request["params"]["attemptID"]
|
readonly attemptID: Endpoint10_5Request["params"]["attemptID"]
|
||||||
readonly location?: Endpoint10_5Request["query"]["location"]
|
readonly location?: Endpoint10_5Request["query"]["location"]
|
||||||
readonly code?: Endpoint10_5Request["payload"]["code"]
|
readonly code?: Endpoint10_5Request["payload"]["code"]
|
||||||
}
|
}
|
||||||
export type Endpoint10_5Output = EffectValue<
|
export type Endpoint10_5Output = EffectValue<ReturnType<RawClient["server.integration"]["integration.oauth.complete"]>>
|
||||||
ReturnType<RawClient["server.integration"]["integration.attempt.complete"]>
|
export type IntegrationOauthCompleteOperation<E = never> = (
|
||||||
>
|
|
||||||
export type IntegrationAttemptCompleteOperation<E = never> = (
|
|
||||||
input: Endpoint10_5Input,
|
input: Endpoint10_5Input,
|
||||||
) => Effect.Effect<Endpoint10_5Output, E>
|
) => Effect.Effect<Endpoint10_5Output, E>
|
||||||
|
|
||||||
type Endpoint10_6Request = Parameters<RawClient["server.integration"]["integration.attempt.cancel"]>[0]
|
type Endpoint10_6Request = Parameters<RawClient["server.integration"]["integration.oauth.cancel"]>[0]
|
||||||
export type Endpoint10_6Input = {
|
export type Endpoint10_6Input = {
|
||||||
|
readonly integrationID: Endpoint10_6Request["params"]["integrationID"]
|
||||||
readonly attemptID: Endpoint10_6Request["params"]["attemptID"]
|
readonly attemptID: Endpoint10_6Request["params"]["attemptID"]
|
||||||
readonly location?: Endpoint10_6Request["query"]["location"]
|
readonly location?: Endpoint10_6Request["query"]["location"]
|
||||||
}
|
}
|
||||||
export type Endpoint10_6Output = EffectValue<ReturnType<RawClient["server.integration"]["integration.attempt.cancel"]>>
|
export type Endpoint10_6Output = EffectValue<ReturnType<RawClient["server.integration"]["integration.oauth.cancel"]>>
|
||||||
export type IntegrationAttemptCancelOperation<E = never> = (
|
export type IntegrationOauthCancelOperation<E = never> = (
|
||||||
input: Endpoint10_6Input,
|
input: Endpoint10_6Input,
|
||||||
) => Effect.Effect<Endpoint10_6Output, E>
|
) => Effect.Effect<Endpoint10_6Output, E>
|
||||||
|
|
||||||
export interface IntegrationApi<E = never> {
|
export interface IntegrationApi<E = never> {
|
||||||
readonly list: IntegrationListOperation<E>
|
readonly list: IntegrationListOperation<E>
|
||||||
readonly get: IntegrationGetOperation<E>
|
readonly get: IntegrationGetOperation<E>
|
||||||
readonly connect: {
|
readonly connect: { readonly key: IntegrationConnectKeyOperation<E> }
|
||||||
readonly key: IntegrationConnectKeyOperation<E>
|
readonly oauth: {
|
||||||
readonly oauth: IntegrationConnectOauthOperation<E>
|
readonly connect: IntegrationOauthConnectOperation<E>
|
||||||
}
|
readonly status: IntegrationOauthStatusOperation<E>
|
||||||
readonly attempt: {
|
readonly complete: IntegrationOauthCompleteOperation<E>
|
||||||
readonly status: IntegrationAttemptStatusOperation<E>
|
readonly cancel: IntegrationOauthCancelOperation<E>
|
||||||
readonly complete: IntegrationAttemptCompleteOperation<E>
|
|
||||||
readonly cancel: IntegrationAttemptCancelOperation<E>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -518,7 +518,7 @@ const Endpoint10_2 = (raw: RawClient["server.integration"]) => (input: Endpoint1
|
|||||||
payload: { key: input["key"], label: input["label"] },
|
payload: { key: input["key"], label: input["label"] },
|
||||||
}).pipe(Effect.mapError(mapClientError))
|
}).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
type Endpoint10_3Request = Parameters<RawClient["server.integration"]["integration.connect.oauth"]>[0]
|
type Endpoint10_3Request = Parameters<RawClient["server.integration"]["integration.oauth.connect"]>[0]
|
||||||
type Endpoint10_3Input = {
|
type Endpoint10_3Input = {
|
||||||
readonly integrationID: Endpoint10_3Request["params"]["integrationID"]
|
readonly integrationID: Endpoint10_3Request["params"]["integrationID"]
|
||||||
readonly location?: Endpoint10_3Request["query"]["location"]
|
readonly location?: Endpoint10_3Request["query"]["location"]
|
||||||
@@ -527,52 +527,60 @@ type Endpoint10_3Input = {
|
|||||||
readonly label?: Endpoint10_3Request["payload"]["label"]
|
readonly label?: Endpoint10_3Request["payload"]["label"]
|
||||||
}
|
}
|
||||||
const Endpoint10_3 = (raw: RawClient["server.integration"]) => (input: Endpoint10_3Input) =>
|
const Endpoint10_3 = (raw: RawClient["server.integration"]) => (input: Endpoint10_3Input) =>
|
||||||
raw["integration.connect.oauth"]({
|
raw["integration.oauth.connect"]({
|
||||||
params: { integrationID: input["integrationID"] },
|
params: { integrationID: input["integrationID"] },
|
||||||
query: { location: input["location"] },
|
query: { location: input["location"] },
|
||||||
payload: { methodID: input["methodID"], inputs: input["inputs"], label: input["label"] },
|
payload: { methodID: input["methodID"], inputs: input["inputs"], label: input["label"] },
|
||||||
}).pipe(Effect.mapError(mapClientError))
|
}).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
type Endpoint10_4Request = Parameters<RawClient["server.integration"]["integration.attempt.status"]>[0]
|
type Endpoint10_4Request = Parameters<RawClient["server.integration"]["integration.oauth.status"]>[0]
|
||||||
type Endpoint10_4Input = {
|
type Endpoint10_4Input = {
|
||||||
|
readonly integrationID: Endpoint10_4Request["params"]["integrationID"]
|
||||||
readonly attemptID: Endpoint10_4Request["params"]["attemptID"]
|
readonly attemptID: Endpoint10_4Request["params"]["attemptID"]
|
||||||
readonly location?: Endpoint10_4Request["query"]["location"]
|
readonly location?: Endpoint10_4Request["query"]["location"]
|
||||||
}
|
}
|
||||||
const Endpoint10_4 = (raw: RawClient["server.integration"]) => (input: Endpoint10_4Input) =>
|
const Endpoint10_4 = (raw: RawClient["server.integration"]) => (input: Endpoint10_4Input) =>
|
||||||
raw["integration.attempt.status"]({
|
raw["integration.oauth.status"]({
|
||||||
params: { attemptID: input["attemptID"] },
|
params: { integrationID: input["integrationID"], attemptID: input["attemptID"] },
|
||||||
query: { location: input["location"] },
|
query: { location: input["location"] },
|
||||||
}).pipe(Effect.mapError(mapClientError))
|
}).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
type Endpoint10_5Request = Parameters<RawClient["server.integration"]["integration.attempt.complete"]>[0]
|
type Endpoint10_5Request = Parameters<RawClient["server.integration"]["integration.oauth.complete"]>[0]
|
||||||
type Endpoint10_5Input = {
|
type Endpoint10_5Input = {
|
||||||
|
readonly integrationID: Endpoint10_5Request["params"]["integrationID"]
|
||||||
readonly attemptID: Endpoint10_5Request["params"]["attemptID"]
|
readonly attemptID: Endpoint10_5Request["params"]["attemptID"]
|
||||||
readonly location?: Endpoint10_5Request["query"]["location"]
|
readonly location?: Endpoint10_5Request["query"]["location"]
|
||||||
readonly code?: Endpoint10_5Request["payload"]["code"]
|
readonly code?: Endpoint10_5Request["payload"]["code"]
|
||||||
}
|
}
|
||||||
const Endpoint10_5 = (raw: RawClient["server.integration"]) => (input: Endpoint10_5Input) =>
|
const Endpoint10_5 = (raw: RawClient["server.integration"]) => (input: Endpoint10_5Input) =>
|
||||||
raw["integration.attempt.complete"]({
|
raw["integration.oauth.complete"]({
|
||||||
params: { attemptID: input["attemptID"] },
|
params: { integrationID: input["integrationID"], attemptID: input["attemptID"] },
|
||||||
query: { location: input["location"] },
|
query: { location: input["location"] },
|
||||||
payload: { code: input["code"] },
|
payload: { code: input["code"] },
|
||||||
}).pipe(Effect.mapError(mapClientError))
|
}).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
type Endpoint10_6Request = Parameters<RawClient["server.integration"]["integration.attempt.cancel"]>[0]
|
type Endpoint10_6Request = Parameters<RawClient["server.integration"]["integration.oauth.cancel"]>[0]
|
||||||
type Endpoint10_6Input = {
|
type Endpoint10_6Input = {
|
||||||
|
readonly integrationID: Endpoint10_6Request["params"]["integrationID"]
|
||||||
readonly attemptID: Endpoint10_6Request["params"]["attemptID"]
|
readonly attemptID: Endpoint10_6Request["params"]["attemptID"]
|
||||||
readonly location?: Endpoint10_6Request["query"]["location"]
|
readonly location?: Endpoint10_6Request["query"]["location"]
|
||||||
}
|
}
|
||||||
const Endpoint10_6 = (raw: RawClient["server.integration"]) => (input: Endpoint10_6Input) =>
|
const Endpoint10_6 = (raw: RawClient["server.integration"]) => (input: Endpoint10_6Input) =>
|
||||||
raw["integration.attempt.cancel"]({
|
raw["integration.oauth.cancel"]({
|
||||||
params: { attemptID: input["attemptID"] },
|
params: { integrationID: input["integrationID"], attemptID: input["attemptID"] },
|
||||||
query: { location: input["location"] },
|
query: { location: input["location"] },
|
||||||
}).pipe(Effect.mapError(mapClientError))
|
}).pipe(Effect.mapError(mapClientError))
|
||||||
|
|
||||||
const adaptGroup10 = (raw: RawClient["server.integration"]) => ({
|
const adaptGroup10 = (raw: RawClient["server.integration"]) => ({
|
||||||
list: Endpoint10_0(raw),
|
list: Endpoint10_0(raw),
|
||||||
get: Endpoint10_1(raw),
|
get: Endpoint10_1(raw),
|
||||||
connect: { key: Endpoint10_2(raw), oauth: Endpoint10_3(raw) },
|
connect: { key: Endpoint10_2(raw) },
|
||||||
attempt: { status: Endpoint10_4(raw), complete: Endpoint10_5(raw), cancel: Endpoint10_6(raw) },
|
oauth: {
|
||||||
|
connect: Endpoint10_3(raw),
|
||||||
|
status: Endpoint10_4(raw),
|
||||||
|
complete: Endpoint10_5(raw),
|
||||||
|
cancel: Endpoint10_6(raw),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
type Endpoint11_0Request = Parameters<RawClient["server.mcp"]["mcp.list"]>[0]
|
type Endpoint11_0Request = Parameters<RawClient["server.mcp"]["mcp.list"]>[0]
|
||||||
|
|||||||
@@ -84,14 +84,14 @@ import type {
|
|||||||
IntegrationGetOutput,
|
IntegrationGetOutput,
|
||||||
IntegrationConnectKeyInput,
|
IntegrationConnectKeyInput,
|
||||||
IntegrationConnectKeyOutput,
|
IntegrationConnectKeyOutput,
|
||||||
IntegrationConnectOauthInput,
|
IntegrationOauthConnectInput,
|
||||||
IntegrationConnectOauthOutput,
|
IntegrationOauthConnectOutput,
|
||||||
IntegrationAttemptStatusInput,
|
IntegrationOauthStatusInput,
|
||||||
IntegrationAttemptStatusOutput,
|
IntegrationOauthStatusOutput,
|
||||||
IntegrationAttemptCompleteInput,
|
IntegrationOauthCompleteInput,
|
||||||
IntegrationAttemptCompleteOutput,
|
IntegrationOauthCompleteOutput,
|
||||||
IntegrationAttemptCancelInput,
|
IntegrationOauthCancelInput,
|
||||||
IntegrationAttemptCancelOutput,
|
IntegrationOauthCancelOutput,
|
||||||
McpListInput,
|
McpListInput,
|
||||||
McpListOutput,
|
McpListOutput,
|
||||||
McpResourceCatalogInput,
|
McpResourceCatalogInput,
|
||||||
@@ -901,8 +901,10 @@ export function make(options: ClientOptions) {
|
|||||||
},
|
},
|
||||||
requestOptions,
|
requestOptions,
|
||||||
),
|
),
|
||||||
oauth: (input: IntegrationConnectOauthInput, requestOptions?: RequestOptions) =>
|
},
|
||||||
request<IntegrationConnectOauthOutput>(
|
oauth: {
|
||||||
|
connect: (input: IntegrationOauthConnectInput, requestOptions?: RequestOptions) =>
|
||||||
|
request<IntegrationOauthConnectOutput>(
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth`,
|
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth`,
|
||||||
@@ -914,13 +916,11 @@ export function make(options: ClientOptions) {
|
|||||||
},
|
},
|
||||||
requestOptions,
|
requestOptions,
|
||||||
),
|
),
|
||||||
},
|
status: (input: IntegrationOauthStatusInput, requestOptions?: RequestOptions) =>
|
||||||
attempt: {
|
request<IntegrationOauthStatusOutput>(
|
||||||
status: (input: IntegrationAttemptStatusInput, requestOptions?: RequestOptions) =>
|
|
||||||
request<IntegrationAttemptStatusOutput>(
|
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`,
|
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth/${encodeURIComponent(input.attemptID)}`,
|
||||||
query: { location: input["location"] },
|
query: { location: input["location"] },
|
||||||
successStatus: 200,
|
successStatus: 200,
|
||||||
declaredStatuses: [401, 400],
|
declaredStatuses: [401, 400],
|
||||||
@@ -928,11 +928,11 @@ export function make(options: ClientOptions) {
|
|||||||
},
|
},
|
||||||
requestOptions,
|
requestOptions,
|
||||||
),
|
),
|
||||||
complete: (input: IntegrationAttemptCompleteInput, requestOptions?: RequestOptions) =>
|
complete: (input: IntegrationOauthCompleteInput, requestOptions?: RequestOptions) =>
|
||||||
request<IntegrationAttemptCompleteOutput>(
|
request<IntegrationOauthCompleteOutput>(
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}/complete`,
|
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth/${encodeURIComponent(input.attemptID)}/complete`,
|
||||||
query: { location: input["location"] },
|
query: { location: input["location"] },
|
||||||
body: { code: input["code"] },
|
body: { code: input["code"] },
|
||||||
successStatus: 204,
|
successStatus: 204,
|
||||||
@@ -941,11 +941,11 @@ export function make(options: ClientOptions) {
|
|||||||
},
|
},
|
||||||
requestOptions,
|
requestOptions,
|
||||||
),
|
),
|
||||||
cancel: (input: IntegrationAttemptCancelInput, requestOptions?: RequestOptions) =>
|
cancel: (input: IntegrationOauthCancelInput, requestOptions?: RequestOptions) =>
|
||||||
request<IntegrationAttemptCancelOutput>(
|
request<IntegrationOauthCancelOutput>(
|
||||||
{
|
{
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`,
|
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth/${encodeURIComponent(input.attemptID)}`,
|
||||||
query: { location: input["location"] },
|
query: { location: input["location"] },
|
||||||
successStatus: 204,
|
successStatus: 204,
|
||||||
declaredStatuses: [401, 400],
|
declaredStatuses: [401, 400],
|
||||||
|
|||||||
@@ -3284,7 +3284,7 @@ export type IntegrationConnectKeyInput = {
|
|||||||
|
|
||||||
export type IntegrationConnectKeyOutput = void
|
export type IntegrationConnectKeyOutput = void
|
||||||
|
|
||||||
export type IntegrationConnectOauthInput = {
|
export type IntegrationOauthConnectInput = {
|
||||||
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
||||||
readonly location?: {
|
readonly location?: {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||||
@@ -3306,7 +3306,7 @@ export type IntegrationConnectOauthInput = {
|
|||||||
}["label"]
|
}["label"]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IntegrationConnectOauthOutput = {
|
export type IntegrationOauthConnectOutput = {
|
||||||
location: { directory: string; workspaceID?: string; project: { id: string; directory: string } }
|
location: { directory: string; workspaceID?: string; project: { id: string; directory: string } }
|
||||||
data: {
|
data: {
|
||||||
attemptID: string
|
attemptID: string
|
||||||
@@ -3317,36 +3317,39 @@ export type IntegrationConnectOauthOutput = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IntegrationAttemptStatusInput = {
|
export type IntegrationOauthStatusInput = {
|
||||||
readonly attemptID: { readonly attemptID: string }["attemptID"]
|
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
||||||
|
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
||||||
readonly location?: {
|
readonly location?: {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||||
}["location"]
|
}["location"]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IntegrationAttemptStatusOutput = {
|
export type IntegrationOauthStatusOutput = {
|
||||||
location: { directory: string; workspaceID?: string; project: { id: string; directory: string } }
|
location: { directory: string; workspaceID?: string; project: { id: string; directory: string } }
|
||||||
data: IntegrationAttemptStatus
|
data: IntegrationAttemptStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IntegrationAttemptCompleteInput = {
|
export type IntegrationOauthCompleteInput = {
|
||||||
readonly attemptID: { readonly attemptID: string }["attemptID"]
|
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
||||||
|
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
||||||
readonly location?: {
|
readonly location?: {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||||
}["location"]
|
}["location"]
|
||||||
readonly code?: { readonly code?: string | undefined }["code"]
|
readonly code?: { readonly code?: string | undefined }["code"]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IntegrationAttemptCompleteOutput = void
|
export type IntegrationOauthCompleteOutput = void
|
||||||
|
|
||||||
export type IntegrationAttemptCancelInput = {
|
export type IntegrationOauthCancelInput = {
|
||||||
readonly attemptID: { readonly attemptID: string }["attemptID"]
|
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
||||||
|
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
||||||
readonly location?: {
|
readonly location?: {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||||
}["location"]
|
}["location"]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IntegrationAttemptCancelOutput = void
|
export type IntegrationOauthCancelOutput = void
|
||||||
|
|
||||||
export type McpListInput = {
|
export type McpListInput = {
|
||||||
readonly location?: {
|
readonly location?: {
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ test("exposes every standard HTTP API group", () => {
|
|||||||
expect(Object.keys(client.debug)).toEqual(["location"])
|
expect(Object.keys(client.debug)).toEqual(["location"])
|
||||||
expect(Object.keys(client.debug.location)).toEqual(["list", "evict"])
|
expect(Object.keys(client.debug.location)).toEqual(["list", "evict"])
|
||||||
expect(Object.keys(client.message)).toEqual(["list"])
|
expect(Object.keys(client.message)).toEqual(["list"])
|
||||||
expect(Object.keys(client.integration)).toEqual(["list", "get", "connect", "attempt"])
|
expect(Object.keys(client.integration)).toEqual(["list", "get", "connect", "oauth"])
|
||||||
expect(Object.keys(client.integration.connect)).toEqual(["key", "oauth"])
|
expect(Object.keys(client.integration.connect)).toEqual(["key"])
|
||||||
expect(Object.keys(client.integration.attempt)).toEqual(["status", "complete", "cancel"])
|
expect(Object.keys(client.integration.oauth)).toEqual(["connect", "status", "complete", "cancel"])
|
||||||
expect(Object.keys(client.file)).toEqual(["read", "list", "find"])
|
expect(Object.keys(client.file)).toEqual(["read", "list", "find"])
|
||||||
expect(Object.keys(client.vcs)).toEqual(["status", "diff"])
|
expect(Object.keys(client.vcs)).toEqual(["status", "diff"])
|
||||||
expect(Object.keys(client.pty)).toEqual(["list", "create", "get", "update", "remove"])
|
expect(Object.keys(client.pty)).toEqual(["list", "create", "get", "update", "remove"])
|
||||||
|
|||||||
@@ -159,17 +159,6 @@ export interface Interface extends State.Transformable<Draft> {
|
|||||||
/** User-facing label for the stored credential. */
|
/** User-facing label for the stored credential. */
|
||||||
readonly label?: string
|
readonly label?: string
|
||||||
}) => Effect.Effect<void, AuthorizationError>
|
}) => Effect.Effect<void, AuthorizationError>
|
||||||
/** Starts a stateful OAuth attempt. */
|
|
||||||
readonly oauth: (input: {
|
|
||||||
/** Integration being authenticated. */
|
|
||||||
readonly integrationID: ID
|
|
||||||
/** OAuth method selected by the caller. */
|
|
||||||
readonly methodID: MethodID
|
|
||||||
/** Answers to the method's optional prompts. */
|
|
||||||
readonly inputs: Inputs
|
|
||||||
/** User-facing label for the credential created on completion. */
|
|
||||||
readonly label?: string
|
|
||||||
}) => Effect.Effect<Attempt, AuthorizationError>
|
|
||||||
/** Updates a stored credential exposed as a connection. */
|
/** Updates a stored credential exposed as a connection. */
|
||||||
readonly update: (
|
readonly update: (
|
||||||
credentialID: Credential.ID,
|
credentialID: Credential.ID,
|
||||||
@@ -178,18 +167,27 @@ export interface Interface extends State.Transformable<Draft> {
|
|||||||
/** Removes a stored credential connection. */
|
/** Removes a stored credential connection. */
|
||||||
readonly remove: (credentialID: Credential.ID) => Effect.Effect<void>
|
readonly remove: (credentialID: Credential.ID) => Effect.Effect<void>
|
||||||
}
|
}
|
||||||
readonly attempt: {
|
readonly oauth: {
|
||||||
|
/** Starts a stateful OAuth attempt. */
|
||||||
|
readonly connect: (input: {
|
||||||
|
readonly integrationID: ID
|
||||||
|
readonly methodID: MethodID
|
||||||
|
readonly inputs: Inputs
|
||||||
|
readonly label?: string
|
||||||
|
}) => Effect.Effect<Attempt, AuthorizationError>
|
||||||
/** Returns the current state of an OAuth attempt. */
|
/** Returns the current state of an OAuth attempt. */
|
||||||
readonly status: (attemptID: AttemptID) => Effect.Effect<AttemptStatus>
|
readonly status: (input: {
|
||||||
|
readonly integrationID: ID
|
||||||
|
readonly attemptID: AttemptID
|
||||||
|
}) => Effect.Effect<AttemptStatus>
|
||||||
/** Completes the attempt and stores its credential. */
|
/** Completes the attempt and stores its credential. */
|
||||||
readonly complete: (input: {
|
readonly complete: (input: {
|
||||||
/** Opaque handle returned by `oauth`. */
|
readonly integrationID: ID
|
||||||
readonly attemptID: AttemptID
|
readonly attemptID: AttemptID
|
||||||
/** Authorization code required by attempts in code mode. */
|
|
||||||
readonly code?: string
|
readonly code?: string
|
||||||
}) => Effect.Effect<void, CodeRequiredError | AuthorizationError>
|
}) => Effect.Effect<void, CodeRequiredError | AuthorizationError>
|
||||||
/** Cancels an attempt and releases its resources. */
|
/** Cancels an attempt and releases its resources. */
|
||||||
readonly cancel: (attemptID: AttemptID) => Effect.Effect<void>
|
readonly cancel: (input: { readonly integrationID: ID; readonly attemptID: AttemptID }) => Effect.Effect<void>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,6 +211,7 @@ type PendingAttempt = {
|
|||||||
}
|
}
|
||||||
type TerminalAttempt = {
|
type TerminalAttempt = {
|
||||||
status: "complete" | "failed" | "expired"
|
status: "complete" | "failed" | "expired"
|
||||||
|
integrationID: ID
|
||||||
message?: string
|
message?: string
|
||||||
removeAt: number
|
removeAt: number
|
||||||
time: AttemptTime
|
time: AttemptTime
|
||||||
@@ -332,6 +331,7 @@ const layer = Layer.effect(
|
|||||||
? { ...match, persisting: true }
|
? { ...match, persisting: true }
|
||||||
: {
|
: {
|
||||||
status: "failed" as const,
|
status: "failed" as const,
|
||||||
|
integrationID: match.integrationID,
|
||||||
message: message(exit.cause),
|
message: message(exit.cause),
|
||||||
time: match.time,
|
time: match.time,
|
||||||
removeAt: now + terminalRetention,
|
removeAt: now + terminalRetention,
|
||||||
@@ -362,13 +362,19 @@ const layer = Layer.effect(
|
|||||||
)
|
)
|
||||||
const settledAt = yield* Clock.currentTimeMillis
|
const settledAt = yield* Clock.currentTimeMillis
|
||||||
const terminal: TerminalAttempt = Exit.isSuccess(persistence)
|
const terminal: TerminalAttempt = Exit.isSuccess(persistence)
|
||||||
? { status: "complete", time: attempt.time, removeAt: settledAt + terminalRetention }
|
? {
|
||||||
|
status: "complete",
|
||||||
|
integrationID: attempt.integrationID,
|
||||||
|
time: attempt.time,
|
||||||
|
removeAt: settledAt + terminalRetention,
|
||||||
|
}
|
||||||
: {
|
: {
|
||||||
status: "failed",
|
status: "failed",
|
||||||
message: message(persistence.cause),
|
integrationID: attempt.integrationID,
|
||||||
time: attempt.time,
|
message: message(persistence.cause),
|
||||||
removeAt: settledAt + terminalRetention,
|
time: attempt.time,
|
||||||
}
|
removeAt: settledAt + terminalRetention,
|
||||||
|
}
|
||||||
// Persisting attempts cannot be cancelled, expired, or claimed again.
|
// Persisting attempts cannot be cancelled, expired, or claimed again.
|
||||||
yield* SynchronizedRef.update(attempts, (current) => new Map(current).set(attemptID, terminal))
|
yield* SynchronizedRef.update(attempts, (current) => new Map(current).set(attemptID, terminal))
|
||||||
if (Exit.isFailure(persistence)) yield* Effect.failCause(persistence.cause)
|
if (Exit.isFailure(persistence)) yield* Effect.failCause(persistence.cause)
|
||||||
@@ -387,7 +393,12 @@ const layer = Layer.effect(
|
|||||||
for (const [id, attempt] of current) {
|
for (const [id, attempt] of current) {
|
||||||
if (attempt.status === "pending" && !attempt.persisting && attempt.time.expires <= now) {
|
if (attempt.status === "pending" && !attempt.persisting && attempt.time.expires <= now) {
|
||||||
scopes.push(attempt.scope)
|
scopes.push(attempt.scope)
|
||||||
next.set(id, { status: "expired", time: attempt.time, removeAt: now + terminalRetention })
|
next.set(id, {
|
||||||
|
status: "expired",
|
||||||
|
integrationID: attempt.integrationID,
|
||||||
|
time: attempt.time,
|
||||||
|
removeAt: now + terminalRetention,
|
||||||
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (attempt.status !== "pending" && attempt.removeAt <= now) next.delete(id)
|
if (attempt.status !== "pending" && attempt.removeAt <= now) next.delete(id)
|
||||||
@@ -399,6 +410,53 @@ const layer = Layer.effect(
|
|||||||
|
|
||||||
yield* scrub().pipe(Effect.repeat(Schedule.spaced(scrubInterval)), Effect.forkIn(scope))
|
yield* scrub().pipe(Effect.repeat(Schedule.spaced(scrubInterval)), Effect.forkIn(scope))
|
||||||
|
|
||||||
|
const connectOAuth = Effect.fn("Integration.oauth.connect")(function* (input: {
|
||||||
|
readonly integrationID: ID
|
||||||
|
readonly methodID: MethodID
|
||||||
|
readonly inputs: Inputs
|
||||||
|
readonly label?: string
|
||||||
|
}) {
|
||||||
|
const method = state.get().integrations.get(input.integrationID)?.implementations.get(input.methodID)
|
||||||
|
if (!method) {
|
||||||
|
return yield* Effect.die(new Error(`OAuth method not found: ${input.integrationID}/${input.methodID}`))
|
||||||
|
}
|
||||||
|
const attemptScope = yield* Scope.fork(scope)
|
||||||
|
const authorization = yield* authorize(method.authorize(input.inputs)).pipe(
|
||||||
|
Scope.provide(attemptScope),
|
||||||
|
Effect.onExit((exit) => (Exit.isFailure(exit) ? Scope.close(attemptScope, exit) : Effect.void)),
|
||||||
|
)
|
||||||
|
const id = AttemptID.create()
|
||||||
|
const created = yield* Clock.currentTimeMillis
|
||||||
|
const time = { created, expires: created + attemptLifetime }
|
||||||
|
yield* SynchronizedRef.update(attempts, (current) =>
|
||||||
|
new Map(current).set(id, {
|
||||||
|
status: "pending",
|
||||||
|
completing: authorization.mode === "auto",
|
||||||
|
persisting: false,
|
||||||
|
authorization,
|
||||||
|
integrationID: input.integrationID,
|
||||||
|
methodID: input.methodID,
|
||||||
|
label: input.label,
|
||||||
|
scope: attemptScope,
|
||||||
|
time,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
if (authorization.mode === "auto") {
|
||||||
|
yield* authorization.callback.pipe(
|
||||||
|
Effect.exit,
|
||||||
|
Effect.flatMap((exit) => settle(id, exit)),
|
||||||
|
Effect.forkIn(attemptScope, { startImmediately: true }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return new Attempt({
|
||||||
|
attemptID: id,
|
||||||
|
url: authorization.url,
|
||||||
|
instructions: authorization.instructions,
|
||||||
|
mode: authorization.mode,
|
||||||
|
time,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
return Service.of({
|
return Service.of({
|
||||||
transform: state.transform,
|
transform: state.transform,
|
||||||
reload: state.reload,
|
reload: state.reload,
|
||||||
@@ -451,47 +509,6 @@ const layer = Layer.effect(
|
|||||||
yield* events.publish(Event.ConnectionUpdated, { integrationID: input.integrationID })
|
yield* events.publish(Event.ConnectionUpdated, { integrationID: input.integrationID })
|
||||||
yield* events.publish(Event.Updated, {})
|
yield* events.publish(Event.Updated, {})
|
||||||
}),
|
}),
|
||||||
oauth: Effect.fn("Integration.connection.oauth")(function* (input) {
|
|
||||||
const method = state.get().integrations.get(input.integrationID)?.implementations.get(input.methodID)
|
|
||||||
if (!method) {
|
|
||||||
return yield* Effect.die(new Error(`OAuth method not found: ${input.integrationID}/${input.methodID}`))
|
|
||||||
}
|
|
||||||
const attemptScope = yield* Scope.fork(scope)
|
|
||||||
const authorization = yield* authorize(method.authorize(input.inputs)).pipe(
|
|
||||||
Scope.provide(attemptScope),
|
|
||||||
Effect.onExit((exit) => (Exit.isFailure(exit) ? Scope.close(attemptScope, exit) : Effect.void)),
|
|
||||||
)
|
|
||||||
const id = AttemptID.create()
|
|
||||||
const created = yield* Clock.currentTimeMillis
|
|
||||||
const time = { created, expires: created + attemptLifetime }
|
|
||||||
yield* SynchronizedRef.update(attempts, (current) =>
|
|
||||||
new Map(current).set(id, {
|
|
||||||
status: "pending",
|
|
||||||
completing: authorization.mode === "auto",
|
|
||||||
persisting: false,
|
|
||||||
authorization,
|
|
||||||
integrationID: input.integrationID,
|
|
||||||
methodID: input.methodID,
|
|
||||||
label: input.label,
|
|
||||||
scope: attemptScope,
|
|
||||||
time,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
if (authorization.mode === "auto") {
|
|
||||||
yield* authorization.callback.pipe(
|
|
||||||
Effect.exit,
|
|
||||||
Effect.flatMap((exit) => settle(id, exit)),
|
|
||||||
Effect.forkIn(attemptScope, { startImmediately: true }),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return new Attempt({
|
|
||||||
attemptID: id,
|
|
||||||
url: authorization.url,
|
|
||||||
instructions: authorization.instructions,
|
|
||||||
mode: authorization.mode,
|
|
||||||
time,
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
update: Effect.fn("Integration.connection.update")(function* (credentialID, updates) {
|
update: Effect.fn("Integration.connection.update")(function* (credentialID, updates) {
|
||||||
const credential = yield* credentials.get(credentialID)
|
const credential = yield* credentials.get(credentialID)
|
||||||
yield* credentials.update(credentialID, updates)
|
yield* credentials.update(credentialID, updates)
|
||||||
@@ -509,19 +526,22 @@ const layer = Layer.effect(
|
|||||||
yield* events.publish(Event.Updated, {})
|
yield* events.publish(Event.Updated, {})
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
attempt: {
|
oauth: {
|
||||||
status: Effect.fn("Integration.attempt.status")(function* (attemptID) {
|
connect: connectOAuth,
|
||||||
const attempt = (yield* SynchronizedRef.get(attempts)).get(attemptID)
|
status: Effect.fn("Integration.oauth.status")(function* (input) {
|
||||||
if (!attempt) return yield* Effect.die(new Error(`OAuth attempt not found: ${attemptID}`))
|
const attempt = (yield* SynchronizedRef.get(attempts)).get(input.attemptID)
|
||||||
|
if (!attempt || attempt.integrationID !== input.integrationID)
|
||||||
|
return yield* Effect.die(new Error(`OAuth attempt not found: ${input.attemptID}`))
|
||||||
if (attempt.status === "failed") {
|
if (attempt.status === "failed") {
|
||||||
return { status: attempt.status, message: attempt.message ?? "Authorization failed", time: attempt.time }
|
return { status: attempt.status, message: attempt.message ?? "Authorization failed", time: attempt.time }
|
||||||
}
|
}
|
||||||
return { status: attempt.status, time: attempt.time }
|
return { status: attempt.status, time: attempt.time }
|
||||||
}),
|
}),
|
||||||
complete: Effect.fn("Integration.attempt.complete")(function* (input) {
|
complete: Effect.fn("Integration.oauth.complete")(function* (input) {
|
||||||
const attempt = yield* SynchronizedRef.modify(attempts, (current) => {
|
const attempt = yield* SynchronizedRef.modify(attempts, (current) => {
|
||||||
const match = current.get(input.attemptID)
|
const match = current.get(input.attemptID)
|
||||||
if (!match || match.status !== "pending" || match.completing) return [match, current]
|
if (!match || match.integrationID !== input.integrationID) return [undefined, current]
|
||||||
|
if (match.status !== "pending" || match.completing) return [match, current]
|
||||||
if (match.authorization.mode === "code" && input.code === undefined) return [match, current]
|
if (match.authorization.mode === "code" && input.code === undefined) return [match, current]
|
||||||
return [match, new Map(current).set(input.attemptID, { ...match, completing: true })]
|
return [match, new Map(current).set(input.attemptID, { ...match, completing: true })]
|
||||||
})
|
})
|
||||||
@@ -540,12 +560,13 @@ const layer = Layer.effect(
|
|||||||
yield* settle(input.attemptID, exit)
|
yield* settle(input.attemptID, exit)
|
||||||
if (Exit.isFailure(exit)) return yield* exit
|
if (Exit.isFailure(exit)) return yield* exit
|
||||||
}),
|
}),
|
||||||
cancel: Effect.fn("Integration.attempt.cancel")(function* (attemptID) {
|
cancel: Effect.fn("Integration.oauth.cancel")(function* (input) {
|
||||||
const attempt = yield* SynchronizedRef.modify(attempts, (current) => {
|
const attempt = yield* SynchronizedRef.modify(attempts, (current) => {
|
||||||
const match = current.get(attemptID)
|
const match = current.get(input.attemptID)
|
||||||
if (!match || match.status !== "pending" || match.persisting) return [undefined, current]
|
if (!match || match.integrationID !== input.integrationID || match.status !== "pending" || match.persisting)
|
||||||
|
return [undefined, current]
|
||||||
const next = new Map(current)
|
const next = new Map(current)
|
||||||
next.delete(attemptID)
|
next.delete(input.attemptID)
|
||||||
return [match, next]
|
return [match, next]
|
||||||
})
|
})
|
||||||
if (attempt) yield* Scope.close(attempt.scope, Exit.void)
|
if (attempt) yield* Scope.close(attempt.scope, Exit.void)
|
||||||
|
|||||||
@@ -173,21 +173,35 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
|||||||
key: input.key,
|
key: input.key,
|
||||||
label: input.label,
|
label: input.label,
|
||||||
}),
|
}),
|
||||||
oauth: (input) =>
|
},
|
||||||
|
oauth: {
|
||||||
|
connect: (input) =>
|
||||||
response(
|
response(
|
||||||
integration.connection.oauth({
|
integration.oauth.connect({
|
||||||
integrationID: Integration.ID.make(input.integrationID),
|
integrationID: Integration.ID.make(input.integrationID),
|
||||||
methodID: Integration.MethodID.make(input.methodID),
|
methodID: Integration.MethodID.make(input.methodID),
|
||||||
inputs: input.inputs,
|
inputs: input.inputs,
|
||||||
label: input.label,
|
label: input.label,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
},
|
status: (input) =>
|
||||||
attempt: {
|
response(
|
||||||
status: (input) => response(integration.attempt.status(Integration.AttemptID.make(input.attemptID))),
|
integration.oauth.status({
|
||||||
|
integrationID: Integration.ID.make(input.integrationID),
|
||||||
|
attemptID: Integration.AttemptID.make(input.attemptID),
|
||||||
|
}),
|
||||||
|
),
|
||||||
complete: (input) =>
|
complete: (input) =>
|
||||||
integration.attempt.complete({ attemptID: Integration.AttemptID.make(input.attemptID), code: input.code }),
|
integration.oauth.complete({
|
||||||
cancel: (input) => integration.attempt.cancel(Integration.AttemptID.make(input.attemptID)),
|
integrationID: Integration.ID.make(input.integrationID),
|
||||||
|
attemptID: Integration.AttemptID.make(input.attemptID),
|
||||||
|
code: input.code,
|
||||||
|
}),
|
||||||
|
cancel: (input) =>
|
||||||
|
integration.oauth.cancel({
|
||||||
|
integrationID: Integration.ID.make(input.integrationID),
|
||||||
|
attemptID: Integration.AttemptID.make(input.attemptID),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
reload: integration.reload,
|
reload: integration.reload,
|
||||||
connection: {
|
connection: {
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ export function fromPromise(plugin: Plugin) {
|
|||||||
catalog: {
|
catalog: {
|
||||||
provider: {
|
provider: {
|
||||||
list: (input) => run(host.catalog.provider.list(input)),
|
list: (input) => run(host.catalog.provider.list(input)),
|
||||||
get: (input) => run(host.catalog.provider.get({ ...input, providerID: Provider.ID.make(input.providerID) })),
|
get: (input) =>
|
||||||
|
run(host.catalog.provider.get({ ...input, providerID: Provider.ID.make(input.providerID) })),
|
||||||
},
|
},
|
||||||
model: {
|
model: {
|
||||||
list: (input) => run(host.catalog.model.list(input)),
|
list: (input) => run(host.catalog.model.list(input)),
|
||||||
@@ -96,35 +97,40 @@ export function fromPromise(plugin: Plugin) {
|
|||||||
),
|
),
|
||||||
connect: {
|
connect: {
|
||||||
key: (input) =>
|
key: (input) =>
|
||||||
run(host.integration.connect.key({ ...input, integrationID: Integration.ID.make(input.integrationID) })),
|
|
||||||
oauth: (input) =>
|
|
||||||
run(
|
run(
|
||||||
host.integration.connect.oauth({
|
host.integration.connect.key({ ...input, integrationID: Integration.ID.make(input.integrationID) }),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
oauth: {
|
||||||
|
connect: (input) =>
|
||||||
|
run(
|
||||||
|
host.integration.oauth.connect({
|
||||||
...input,
|
...input,
|
||||||
integrationID: Integration.ID.make(input.integrationID),
|
integrationID: Integration.ID.make(input.integrationID),
|
||||||
methodID: Integration.MethodID.make(input.methodID),
|
methodID: Integration.MethodID.make(input.methodID),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
},
|
|
||||||
attempt: {
|
|
||||||
status: (input) =>
|
status: (input) =>
|
||||||
run(
|
run(
|
||||||
host.integration.attempt.status({
|
host.integration.oauth.status({
|
||||||
...input,
|
...input,
|
||||||
|
integrationID: Integration.ID.make(input.integrationID),
|
||||||
attemptID: Integration.AttemptID.make(input.attemptID),
|
attemptID: Integration.AttemptID.make(input.attemptID),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
complete: (input) =>
|
complete: (input) =>
|
||||||
run(
|
run(
|
||||||
host.integration.attempt.complete({
|
host.integration.oauth.complete({
|
||||||
...input,
|
...input,
|
||||||
|
integrationID: Integration.ID.make(input.integrationID),
|
||||||
attemptID: Integration.AttemptID.make(input.attemptID),
|
attemptID: Integration.AttemptID.make(input.attemptID),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
cancel: (input) =>
|
cancel: (input) =>
|
||||||
run(
|
run(
|
||||||
host.integration.attempt.cancel({
|
host.integration.oauth.cancel({
|
||||||
...input,
|
...input,
|
||||||
|
integrationID: Integration.ID.make(input.integrationID),
|
||||||
attemptID: Integration.AttemptID.make(input.attemptID),
|
attemptID: Integration.AttemptID.make(input.attemptID),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -181,14 +181,14 @@ describe("Integration", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const attempt = yield* integrations.connection.oauth({
|
const attempt = yield* integrations.oauth.connect({
|
||||||
integrationID,
|
integrationID,
|
||||||
methodID,
|
methodID,
|
||||||
inputs: {},
|
inputs: {},
|
||||||
label: "Personal",
|
label: "Personal",
|
||||||
})
|
})
|
||||||
expect(attempt.mode).toBe("code")
|
expect(attempt.mode).toBe("code")
|
||||||
yield* integrations.attempt.complete({ attemptID: attempt.attemptID, code: "1234" })
|
yield* integrations.oauth.complete({ integrationID, attemptID: attempt.attemptID, code: "1234" })
|
||||||
|
|
||||||
expect((yield* credentials.list(integrationID))[0]).toEqual(
|
expect((yield* credentials.list(integrationID))[0]).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@@ -230,12 +230,17 @@ describe("Integration", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const attempt = yield* integrations.connection.oauth({ integrationID, methodID, inputs: {} })
|
const attempt = yield* integrations.oauth.connect({ integrationID, methodID, inputs: {} })
|
||||||
expect(yield* integrations.attempt.complete({ attemptID: attempt.attemptID }).pipe(Effect.flip)).toBeInstanceOf(
|
expect(
|
||||||
Integration.CodeRequiredError,
|
yield* integrations.oauth.complete({ integrationID, attemptID: attempt.attemptID }).pipe(Effect.flip),
|
||||||
)
|
).toBeInstanceOf(Integration.CodeRequiredError)
|
||||||
expect(closed).toBe(false)
|
expect(closed).toBe(false)
|
||||||
yield* integrations.attempt.cancel(attempt.attemptID)
|
yield* integrations.oauth.cancel({
|
||||||
|
integrationID: Integration.ID.make("other"),
|
||||||
|
attemptID: attempt.attemptID,
|
||||||
|
})
|
||||||
|
expect(closed).toBe(false)
|
||||||
|
yield* integrations.oauth.cancel({ integrationID, attemptID: attempt.attemptID })
|
||||||
expect(closed).toBe(true)
|
expect(closed).toBe(true)
|
||||||
expect(yield* credentials.list(integrationID)).toEqual([])
|
expect(yield* credentials.list(integrationID)).toEqual([])
|
||||||
}),
|
}),
|
||||||
@@ -263,9 +268,9 @@ describe("Integration", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const attempt = yield* integrations.connection.oauth({ integrationID, methodID, inputs: {} })
|
const attempt = yield* integrations.oauth.connect({ integrationID, methodID, inputs: {} })
|
||||||
yield* Effect.yieldNow
|
yield* Effect.yieldNow
|
||||||
expect(yield* integrations.attempt.status(attempt.attemptID)).toEqual({
|
expect(yield* integrations.oauth.status({ integrationID, attemptID: attempt.attemptID })).toEqual({
|
||||||
status: "complete",
|
status: "complete",
|
||||||
time: attempt.time,
|
time: attempt.time,
|
||||||
})
|
})
|
||||||
@@ -301,12 +306,12 @@ describe("Integration", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const attempt = yield* integrations.connection.oauth({ integrationID, methodID, inputs: {} })
|
const attempt = yield* integrations.oauth.connect({ integrationID, methodID, inputs: {} })
|
||||||
const exit = yield* integrations.attempt
|
const exit = yield* integrations.oauth
|
||||||
.complete({ attemptID: attempt.attemptID, code: "1234" })
|
.complete({ integrationID, attemptID: attempt.attemptID, code: "1234" })
|
||||||
.pipe(Effect.exit)
|
.pipe(Effect.exit)
|
||||||
expect(Exit.isFailure(exit) && Cause.hasDies(exit.cause)).toBe(true)
|
expect(Exit.isFailure(exit) && Cause.hasDies(exit.cause)).toBe(true)
|
||||||
expect(yield* integrations.attempt.status(attempt.attemptID)).toEqual({
|
expect(yield* integrations.oauth.status({ integrationID, attemptID: attempt.attemptID })).toEqual({
|
||||||
status: "failed",
|
status: "failed",
|
||||||
message: "credential persistence failed",
|
message: "credential persistence failed",
|
||||||
time: attempt.time,
|
time: attempt.time,
|
||||||
@@ -337,11 +342,11 @@ describe("Integration", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const attempt = yield* integrations.connection.oauth({ integrationID, methodID, inputs: {} })
|
const attempt = yield* integrations.oauth.connect({ integrationID, methodID, inputs: {} })
|
||||||
expect(attempt.time.expires - attempt.time.created).toBe(Duration.toMillis(Duration.minutes(10)))
|
expect(attempt.time.expires - attempt.time.created).toBe(Duration.toMillis(Duration.minutes(10)))
|
||||||
yield* TestClock.adjust(Duration.minutes(10))
|
yield* TestClock.adjust(Duration.minutes(10))
|
||||||
yield* Effect.yieldNow
|
yield* Effect.yieldNow
|
||||||
expect(yield* integrations.attempt.status(attempt.attemptID)).toEqual({
|
expect(yield* integrations.oauth.status({ integrationID, attemptID: attempt.attemptID })).toEqual({
|
||||||
status: "expired",
|
status: "expired",
|
||||||
time: attempt.time,
|
time: attempt.time,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -187,11 +187,11 @@ function resourceMcpLayer(url: string, onFormCreated?: (form: Form.Info) => Effe
|
|||||||
active: unusedIntegration,
|
active: unusedIntegration,
|
||||||
resolve: unusedIntegration,
|
resolve: unusedIntegration,
|
||||||
key: unusedIntegration,
|
key: unusedIntegration,
|
||||||
oauth: unusedIntegration,
|
|
||||||
update: unusedIntegration,
|
update: unusedIntegration,
|
||||||
remove: unusedIntegration,
|
remove: unusedIntegration,
|
||||||
},
|
},
|
||||||
attempt: {
|
oauth: {
|
||||||
|
connect: unusedIntegration,
|
||||||
status: unusedIntegration,
|
status: unusedIntegration,
|
||||||
complete: unusedIntegration,
|
complete: unusedIntegration,
|
||||||
cancel: unusedIntegration,
|
cancel: unusedIntegration,
|
||||||
|
|||||||
@@ -48,12 +48,12 @@ export function host(overrides: Overrides = {}): PluginContext {
|
|||||||
get: () => Effect.die("unused integration.get"),
|
get: () => Effect.die("unused integration.get"),
|
||||||
connect: {
|
connect: {
|
||||||
key: () => Effect.die("unused integration.connect.key"),
|
key: () => Effect.die("unused integration.connect.key"),
|
||||||
oauth: () => Effect.die("unused integration.connect.oauth"),
|
|
||||||
},
|
},
|
||||||
attempt: {
|
oauth: {
|
||||||
status: () => Effect.die("unused integration.attempt.status"),
|
connect: () => Effect.die("unused integration.oauth.connect"),
|
||||||
complete: () => Effect.die("unused integration.attempt.complete"),
|
status: () => Effect.die("unused integration.oauth.status"),
|
||||||
cancel: () => Effect.die("unused integration.attempt.cancel"),
|
complete: () => Effect.die("unused integration.oauth.complete"),
|
||||||
|
cancel: () => Effect.die("unused integration.oauth.cancel"),
|
||||||
},
|
},
|
||||||
transform: () => Effect.die("unused integration.transform"),
|
transform: () => Effect.die("unused integration.transform"),
|
||||||
reload: () => Effect.die("unused integration.reload"),
|
reload: () => Effect.die("unused integration.reload"),
|
||||||
@@ -193,12 +193,12 @@ export function integrationHost(integration: Integration.Interface): PluginConte
|
|||||||
get: () => Effect.die("unused integration.get"),
|
get: () => Effect.die("unused integration.get"),
|
||||||
connect: {
|
connect: {
|
||||||
key: () => Effect.die("unused integration.connect.key"),
|
key: () => Effect.die("unused integration.connect.key"),
|
||||||
oauth: () => Effect.die("unused integration.connect.oauth"),
|
|
||||||
},
|
},
|
||||||
attempt: {
|
oauth: {
|
||||||
status: () => Effect.die("unused integration.attempt.status"),
|
connect: () => Effect.die("unused integration.oauth.connect"),
|
||||||
complete: () => Effect.die("unused integration.attempt.complete"),
|
status: () => Effect.die("unused integration.oauth.status"),
|
||||||
cancel: () => Effect.die("unused integration.attempt.cancel"),
|
complete: () => Effect.die("unused integration.oauth.complete"),
|
||||||
|
cancel: () => Effect.die("unused integration.oauth.cancel"),
|
||||||
},
|
},
|
||||||
reload: integration.reload,
|
reload: integration.reload,
|
||||||
connection: {
|
connection: {
|
||||||
|
|||||||
@@ -124,13 +124,17 @@ describe("OpencodePlugin", () => {
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const integrations = yield* Integration.Service
|
const integrations = yield* Integration.Service
|
||||||
const attempt = yield* integrations.connection.oauth({
|
const integrationID = Integration.ID.make("opencode")
|
||||||
integrationID: Integration.ID.make("opencode"),
|
const attempt = yield* integrations.oauth.connect({
|
||||||
|
integrationID,
|
||||||
methodID: Integration.MethodID.make("device"),
|
methodID: Integration.MethodID.make("device"),
|
||||||
inputs: { server: `${server.url.origin}/console///?ignored=true#ignored` },
|
inputs: { server: `${server.url.origin}/console///?ignored=true#ignored` },
|
||||||
})
|
})
|
||||||
expect(attempt.url).toBe(`${server.url.origin}/verify`)
|
expect(attempt.url).toBe(`${server.url.origin}/verify`)
|
||||||
yield* eventually(integrations.attempt.status(attempt.attemptID), (status) => status.status === "complete")
|
yield* eventually(
|
||||||
|
integrations.oauth.status({ integrationID, attemptID: attempt.attemptID }),
|
||||||
|
(status) => status.status === "complete",
|
||||||
|
)
|
||||||
|
|
||||||
expect(requests).toContain("POST /console/auth/device/code")
|
expect(requests).toContain("POST /console/auth/device/code")
|
||||||
expect(requests).toContain("POST /console/auth/device/token")
|
expect(requests).toContain("POST /console/auth/device/token")
|
||||||
@@ -147,8 +151,8 @@ describe("OpencodePlugin", () => {
|
|||||||
it.effect("rejects non-HTTP OpenCode servers", () =>
|
it.effect("rejects non-HTTP OpenCode servers", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* addPlugin()
|
yield* addPlugin()
|
||||||
const error = yield* (yield* Integration.Service).connection
|
const error = yield* (yield* Integration.Service).oauth
|
||||||
.oauth({
|
.connect({
|
||||||
integrationID: Integration.ID.make("opencode"),
|
integrationID: Integration.ID.make("opencode"),
|
||||||
methodID: Integration.MethodID.make("device"),
|
methodID: Integration.MethodID.make("device"),
|
||||||
inputs: { server: "ftp://console.example.com" },
|
inputs: { server: "ftp://console.example.com" },
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const IntegrationGroup = HttpApiGroup.make("server.integration")
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.add(
|
.add(
|
||||||
HttpApiEndpoint.post("integration.connect.oauth", "/api/integration/:integrationID/connect/oauth", {
|
HttpApiEndpoint.post("integration.oauth.connect", "/api/integration/:integrationID/connect/oauth", {
|
||||||
params: { integrationID: Integration.ID },
|
params: { integrationID: Integration.ID },
|
||||||
query: LocationQuery,
|
query: LocationQuery,
|
||||||
payload: Schema.Struct({
|
payload: Schema.Struct({
|
||||||
@@ -72,54 +72,58 @@ export const IntegrationGroup = HttpApiGroup.make("server.integration")
|
|||||||
.annotateMerge(locationQueryOpenApi)
|
.annotateMerge(locationQueryOpenApi)
|
||||||
.annotateMerge(
|
.annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "v2.integration.connect.oauth",
|
identifier: "v2.integration.oauth.connect",
|
||||||
summary: "Begin OAuth connection",
|
summary: "Begin OAuth connection",
|
||||||
description: "Start an OAuth attempt and return the authorization details.",
|
description: "Start an OAuth attempt and return the authorization details.",
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.add(
|
.add(
|
||||||
HttpApiEndpoint.get("integration.attempt.status", "/api/integration/attempt/:attemptID", {
|
HttpApiEndpoint.get("integration.oauth.status", "/api/integration/:integrationID/connect/oauth/:attemptID", {
|
||||||
params: { attemptID: Integration.AttemptID },
|
params: { integrationID: Integration.ID, attemptID: Integration.AttemptID },
|
||||||
query: LocationQuery,
|
query: LocationQuery,
|
||||||
success: Location.response(Integration.AttemptStatus),
|
success: Location.response(Integration.AttemptStatus),
|
||||||
})
|
})
|
||||||
.annotateMerge(locationQueryOpenApi)
|
.annotateMerge(locationQueryOpenApi)
|
||||||
.annotateMerge(
|
.annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "v2.integration.attempt.status",
|
identifier: "v2.integration.oauth.status",
|
||||||
summary: "Get OAuth attempt status",
|
summary: "Get OAuth attempt status",
|
||||||
description: "Poll the current status of an OAuth attempt.",
|
description: "Poll the current status of an OAuth attempt.",
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.add(
|
.add(
|
||||||
HttpApiEndpoint.post("integration.attempt.complete", "/api/integration/attempt/:attemptID/complete", {
|
HttpApiEndpoint.post(
|
||||||
params: { attemptID: Integration.AttemptID },
|
"integration.oauth.complete",
|
||||||
query: LocationQuery,
|
"/api/integration/:integrationID/connect/oauth/:attemptID/complete",
|
||||||
payload: Schema.Struct({ code: Schema.optional(Schema.String) }),
|
{
|
||||||
success: HttpApiSchema.NoContent,
|
params: { integrationID: Integration.ID, attemptID: Integration.AttemptID },
|
||||||
error: InvalidRequestError,
|
query: LocationQuery,
|
||||||
})
|
payload: Schema.Struct({ code: Schema.optional(Schema.String) }),
|
||||||
|
success: HttpApiSchema.NoContent,
|
||||||
|
error: InvalidRequestError,
|
||||||
|
},
|
||||||
|
)
|
||||||
.annotateMerge(locationQueryOpenApi)
|
.annotateMerge(locationQueryOpenApi)
|
||||||
.annotateMerge(
|
.annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "v2.integration.attempt.complete",
|
identifier: "v2.integration.oauth.complete",
|
||||||
summary: "Complete OAuth connection",
|
summary: "Complete OAuth connection",
|
||||||
description: "Complete a code-based OAuth attempt and store the resulting credential.",
|
description: "Complete a code-based OAuth attempt and store the resulting credential.",
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.add(
|
.add(
|
||||||
HttpApiEndpoint.delete("integration.attempt.cancel", "/api/integration/attempt/:attemptID", {
|
HttpApiEndpoint.delete("integration.oauth.cancel", "/api/integration/:integrationID/connect/oauth/:attemptID", {
|
||||||
params: { attemptID: Integration.AttemptID },
|
params: { integrationID: Integration.ID, attemptID: Integration.AttemptID },
|
||||||
query: LocationQuery,
|
query: LocationQuery,
|
||||||
success: HttpApiSchema.NoContent,
|
success: HttpApiSchema.NoContent,
|
||||||
})
|
})
|
||||||
.annotateMerge(locationQueryOpenApi)
|
.annotateMerge(locationQueryOpenApi)
|
||||||
.annotateMerge(
|
.annotateMerge(
|
||||||
OpenApi.annotations({
|
OpenApi.annotations({
|
||||||
identifier: "v2.integration.attempt.cancel",
|
identifier: "v2.integration.oauth.cancel",
|
||||||
summary: "Cancel OAuth connection",
|
summary: "Cancel OAuth connection",
|
||||||
description: "Cancel an OAuth attempt and release its resources.",
|
description: "Cancel an OAuth attempt and release its resources.",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -294,20 +294,20 @@ import type {
|
|||||||
V2HealthGetResponses,
|
V2HealthGetResponses,
|
||||||
V2HealthStopErrors,
|
V2HealthStopErrors,
|
||||||
V2HealthStopResponses,
|
V2HealthStopResponses,
|
||||||
V2IntegrationAttemptCancelErrors,
|
|
||||||
V2IntegrationAttemptCancelResponses,
|
|
||||||
V2IntegrationAttemptCompleteErrors,
|
|
||||||
V2IntegrationAttemptCompleteResponses,
|
|
||||||
V2IntegrationAttemptStatusErrors,
|
|
||||||
V2IntegrationAttemptStatusResponses,
|
|
||||||
V2IntegrationConnectKeyErrors,
|
V2IntegrationConnectKeyErrors,
|
||||||
V2IntegrationConnectKeyResponses,
|
V2IntegrationConnectKeyResponses,
|
||||||
V2IntegrationConnectOauthErrors,
|
|
||||||
V2IntegrationConnectOauthResponses,
|
|
||||||
V2IntegrationGetErrors,
|
V2IntegrationGetErrors,
|
||||||
V2IntegrationGetResponses,
|
V2IntegrationGetResponses,
|
||||||
V2IntegrationListErrors,
|
V2IntegrationListErrors,
|
||||||
V2IntegrationListResponses,
|
V2IntegrationListResponses,
|
||||||
|
V2IntegrationOauthCancelErrors,
|
||||||
|
V2IntegrationOauthCancelResponses,
|
||||||
|
V2IntegrationOauthCompleteErrors,
|
||||||
|
V2IntegrationOauthCompleteResponses,
|
||||||
|
V2IntegrationOauthConnectErrors,
|
||||||
|
V2IntegrationOauthConnectResponses,
|
||||||
|
V2IntegrationOauthStatusErrors,
|
||||||
|
V2IntegrationOauthStatusResponses,
|
||||||
V2LocationGetErrors,
|
V2LocationGetErrors,
|
||||||
V2LocationGetResponses,
|
V2LocationGetResponses,
|
||||||
V2McpListErrors,
|
V2McpListErrors,
|
||||||
@@ -6827,13 +6827,15 @@ export class Connect extends HeyApiClient {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Oauth2 extends HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* Begin OAuth connection
|
* Begin OAuth connection
|
||||||
*
|
*
|
||||||
* Start an OAuth attempt and return the authorization details.
|
* Start an OAuth attempt and return the authorization details.
|
||||||
*/
|
*/
|
||||||
public oauth<ThrowOnError extends boolean = false>(
|
public connect<ThrowOnError extends boolean = false>(
|
||||||
parameters: {
|
parameters: {
|
||||||
integrationID: string
|
integrationID: string
|
||||||
location?: {
|
location?: {
|
||||||
@@ -6863,8 +6865,8 @@ export class Connect extends HeyApiClient {
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
return (options?.client ?? this.client).post<
|
return (options?.client ?? this.client).post<
|
||||||
V2IntegrationConnectOauthResponses,
|
V2IntegrationOauthConnectResponses,
|
||||||
V2IntegrationConnectOauthErrors,
|
V2IntegrationOauthConnectErrors,
|
||||||
ThrowOnError
|
ThrowOnError
|
||||||
>({
|
>({
|
||||||
url: "/api/integration/{integrationID}/connect/oauth",
|
url: "/api/integration/{integrationID}/connect/oauth",
|
||||||
@@ -6877,9 +6879,7 @@ export class Connect extends HeyApiClient {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export class Attempt extends HeyApiClient {
|
|
||||||
/**
|
/**
|
||||||
* Cancel OAuth connection
|
* Cancel OAuth connection
|
||||||
*
|
*
|
||||||
@@ -6887,6 +6887,7 @@ export class Attempt extends HeyApiClient {
|
|||||||
*/
|
*/
|
||||||
public cancel<ThrowOnError extends boolean = false>(
|
public cancel<ThrowOnError extends boolean = false>(
|
||||||
parameters: {
|
parameters: {
|
||||||
|
integrationID: string
|
||||||
attemptID: string
|
attemptID: string
|
||||||
location?: {
|
location?: {
|
||||||
directory?: string | null
|
directory?: string | null
|
||||||
@@ -6900,6 +6901,7 @@ export class Attempt extends HeyApiClient {
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
args: [
|
args: [
|
||||||
|
{ in: "path", key: "integrationID" },
|
||||||
{ in: "path", key: "attemptID" },
|
{ in: "path", key: "attemptID" },
|
||||||
{ in: "query", key: "location" },
|
{ in: "query", key: "location" },
|
||||||
],
|
],
|
||||||
@@ -6907,11 +6909,11 @@ export class Attempt extends HeyApiClient {
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
return (options?.client ?? this.client).delete<
|
return (options?.client ?? this.client).delete<
|
||||||
V2IntegrationAttemptCancelResponses,
|
V2IntegrationOauthCancelResponses,
|
||||||
V2IntegrationAttemptCancelErrors,
|
V2IntegrationOauthCancelErrors,
|
||||||
ThrowOnError
|
ThrowOnError
|
||||||
>({
|
>({
|
||||||
url: "/api/integration/attempt/{attemptID}",
|
url: "/api/integration/{integrationID}/connect/oauth/{attemptID}",
|
||||||
...options,
|
...options,
|
||||||
...params,
|
...params,
|
||||||
})
|
})
|
||||||
@@ -6924,6 +6926,7 @@ export class Attempt extends HeyApiClient {
|
|||||||
*/
|
*/
|
||||||
public status<ThrowOnError extends boolean = false>(
|
public status<ThrowOnError extends boolean = false>(
|
||||||
parameters: {
|
parameters: {
|
||||||
|
integrationID: string
|
||||||
attemptID: string
|
attemptID: string
|
||||||
location?: {
|
location?: {
|
||||||
directory?: string | null
|
directory?: string | null
|
||||||
@@ -6937,6 +6940,7 @@ export class Attempt extends HeyApiClient {
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
args: [
|
args: [
|
||||||
|
{ in: "path", key: "integrationID" },
|
||||||
{ in: "path", key: "attemptID" },
|
{ in: "path", key: "attemptID" },
|
||||||
{ in: "query", key: "location" },
|
{ in: "query", key: "location" },
|
||||||
],
|
],
|
||||||
@@ -6944,11 +6948,11 @@ export class Attempt extends HeyApiClient {
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
return (options?.client ?? this.client).get<
|
return (options?.client ?? this.client).get<
|
||||||
V2IntegrationAttemptStatusResponses,
|
V2IntegrationOauthStatusResponses,
|
||||||
V2IntegrationAttemptStatusErrors,
|
V2IntegrationOauthStatusErrors,
|
||||||
ThrowOnError
|
ThrowOnError
|
||||||
>({
|
>({
|
||||||
url: "/api/integration/attempt/{attemptID}",
|
url: "/api/integration/{integrationID}/connect/oauth/{attemptID}",
|
||||||
...options,
|
...options,
|
||||||
...params,
|
...params,
|
||||||
})
|
})
|
||||||
@@ -6961,6 +6965,7 @@ export class Attempt extends HeyApiClient {
|
|||||||
*/
|
*/
|
||||||
public complete<ThrowOnError extends boolean = false>(
|
public complete<ThrowOnError extends boolean = false>(
|
||||||
parameters: {
|
parameters: {
|
||||||
|
integrationID: string
|
||||||
attemptID: string
|
attemptID: string
|
||||||
location?: {
|
location?: {
|
||||||
directory?: string | null
|
directory?: string | null
|
||||||
@@ -6975,6 +6980,7 @@ export class Attempt extends HeyApiClient {
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
args: [
|
args: [
|
||||||
|
{ in: "path", key: "integrationID" },
|
||||||
{ in: "path", key: "attemptID" },
|
{ in: "path", key: "attemptID" },
|
||||||
{ in: "query", key: "location" },
|
{ in: "query", key: "location" },
|
||||||
{ in: "body", key: "code" },
|
{ in: "body", key: "code" },
|
||||||
@@ -6983,11 +6989,11 @@ export class Attempt extends HeyApiClient {
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
return (options?.client ?? this.client).post<
|
return (options?.client ?? this.client).post<
|
||||||
V2IntegrationAttemptCompleteResponses,
|
V2IntegrationOauthCompleteResponses,
|
||||||
V2IntegrationAttemptCompleteErrors,
|
V2IntegrationOauthCompleteErrors,
|
||||||
ThrowOnError
|
ThrowOnError
|
||||||
>({
|
>({
|
||||||
url: "/api/integration/attempt/{attemptID}/complete",
|
url: "/api/integration/{integrationID}/connect/oauth/{attemptID}/complete",
|
||||||
...options,
|
...options,
|
||||||
...params,
|
...params,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -7060,9 +7066,9 @@ export class Integration extends HeyApiClient {
|
|||||||
return (this._connect ??= new Connect({ client: this.client }))
|
return (this._connect ??= new Connect({ client: this.client }))
|
||||||
}
|
}
|
||||||
|
|
||||||
private _attempt?: Attempt
|
private _oauth?: Oauth2
|
||||||
get attempt(): Attempt {
|
get oauth(): Oauth2 {
|
||||||
return (this._attempt ??= new Attempt({ client: this.client }))
|
return (this._oauth ??= new Oauth2({ client: this.client }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16846,7 +16846,7 @@ export type V2IntegrationConnectKeyResponses = {
|
|||||||
|
|
||||||
export type V2IntegrationConnectKeyResponse = V2IntegrationConnectKeyResponses[keyof V2IntegrationConnectKeyResponses]
|
export type V2IntegrationConnectKeyResponse = V2IntegrationConnectKeyResponses[keyof V2IntegrationConnectKeyResponses]
|
||||||
|
|
||||||
export type V2IntegrationConnectOauthData = {
|
export type V2IntegrationOauthConnectData = {
|
||||||
body: {
|
body: {
|
||||||
methodID: string
|
methodID: string
|
||||||
inputs: {
|
inputs: {
|
||||||
@@ -16866,7 +16866,7 @@ export type V2IntegrationConnectOauthData = {
|
|||||||
url: "/api/integration/{integrationID}/connect/oauth"
|
url: "/api/integration/{integrationID}/connect/oauth"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type V2IntegrationConnectOauthErrors = {
|
export type V2IntegrationOauthConnectErrors = {
|
||||||
/**
|
/**
|
||||||
* InvalidRequestError
|
* InvalidRequestError
|
||||||
*/
|
*/
|
||||||
@@ -16877,9 +16877,9 @@ export type V2IntegrationConnectOauthErrors = {
|
|||||||
401: UnauthorizedError
|
401: UnauthorizedError
|
||||||
}
|
}
|
||||||
|
|
||||||
export type V2IntegrationConnectOauthError = V2IntegrationConnectOauthErrors[keyof V2IntegrationConnectOauthErrors]
|
export type V2IntegrationOauthConnectError = V2IntegrationOauthConnectErrors[keyof V2IntegrationOauthConnectErrors]
|
||||||
|
|
||||||
export type V2IntegrationConnectOauthResponses = {
|
export type V2IntegrationOauthConnectResponses = {
|
||||||
/**
|
/**
|
||||||
* Success
|
* Success
|
||||||
*/
|
*/
|
||||||
@@ -16889,12 +16889,13 @@ export type V2IntegrationConnectOauthResponses = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type V2IntegrationConnectOauthResponse =
|
export type V2IntegrationOauthConnectResponse =
|
||||||
V2IntegrationConnectOauthResponses[keyof V2IntegrationConnectOauthResponses]
|
V2IntegrationOauthConnectResponses[keyof V2IntegrationOauthConnectResponses]
|
||||||
|
|
||||||
export type V2IntegrationAttemptCancelData = {
|
export type V2IntegrationOauthCancelData = {
|
||||||
body?: never
|
body?: never
|
||||||
path: {
|
path: {
|
||||||
|
integrationID: string
|
||||||
attemptID: string
|
attemptID: string
|
||||||
}
|
}
|
||||||
query?: {
|
query?: {
|
||||||
@@ -16903,10 +16904,10 @@ export type V2IntegrationAttemptCancelData = {
|
|||||||
workspace?: string | null
|
workspace?: string | null
|
||||||
} | null
|
} | null
|
||||||
}
|
}
|
||||||
url: "/api/integration/attempt/{attemptID}"
|
url: "/api/integration/{integrationID}/connect/oauth/{attemptID}"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type V2IntegrationAttemptCancelErrors = {
|
export type V2IntegrationOauthCancelErrors = {
|
||||||
/**
|
/**
|
||||||
* InvalidRequestError
|
* InvalidRequestError
|
||||||
*/
|
*/
|
||||||
@@ -16917,21 +16918,22 @@ export type V2IntegrationAttemptCancelErrors = {
|
|||||||
401: UnauthorizedError
|
401: UnauthorizedError
|
||||||
}
|
}
|
||||||
|
|
||||||
export type V2IntegrationAttemptCancelError = V2IntegrationAttemptCancelErrors[keyof V2IntegrationAttemptCancelErrors]
|
export type V2IntegrationOauthCancelError = V2IntegrationOauthCancelErrors[keyof V2IntegrationOauthCancelErrors]
|
||||||
|
|
||||||
export type V2IntegrationAttemptCancelResponses = {
|
export type V2IntegrationOauthCancelResponses = {
|
||||||
/**
|
/**
|
||||||
* <No Content>
|
* <No Content>
|
||||||
*/
|
*/
|
||||||
204: void
|
204: void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type V2IntegrationAttemptCancelResponse =
|
export type V2IntegrationOauthCancelResponse =
|
||||||
V2IntegrationAttemptCancelResponses[keyof V2IntegrationAttemptCancelResponses]
|
V2IntegrationOauthCancelResponses[keyof V2IntegrationOauthCancelResponses]
|
||||||
|
|
||||||
export type V2IntegrationAttemptStatusData = {
|
export type V2IntegrationOauthStatusData = {
|
||||||
body?: never
|
body?: never
|
||||||
path: {
|
path: {
|
||||||
|
integrationID: string
|
||||||
attemptID: string
|
attemptID: string
|
||||||
}
|
}
|
||||||
query?: {
|
query?: {
|
||||||
@@ -16940,10 +16942,10 @@ export type V2IntegrationAttemptStatusData = {
|
|||||||
workspace?: string | null
|
workspace?: string | null
|
||||||
} | null
|
} | null
|
||||||
}
|
}
|
||||||
url: "/api/integration/attempt/{attemptID}"
|
url: "/api/integration/{integrationID}/connect/oauth/{attemptID}"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type V2IntegrationAttemptStatusErrors = {
|
export type V2IntegrationOauthStatusErrors = {
|
||||||
/**
|
/**
|
||||||
* InvalidRequestError
|
* InvalidRequestError
|
||||||
*/
|
*/
|
||||||
@@ -16954,9 +16956,9 @@ export type V2IntegrationAttemptStatusErrors = {
|
|||||||
401: UnauthorizedError
|
401: UnauthorizedError
|
||||||
}
|
}
|
||||||
|
|
||||||
export type V2IntegrationAttemptStatusError = V2IntegrationAttemptStatusErrors[keyof V2IntegrationAttemptStatusErrors]
|
export type V2IntegrationOauthStatusError = V2IntegrationOauthStatusErrors[keyof V2IntegrationOauthStatusErrors]
|
||||||
|
|
||||||
export type V2IntegrationAttemptStatusResponses = {
|
export type V2IntegrationOauthStatusResponses = {
|
||||||
/**
|
/**
|
||||||
* Success
|
* Success
|
||||||
*/
|
*/
|
||||||
@@ -16966,14 +16968,15 @@ export type V2IntegrationAttemptStatusResponses = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type V2IntegrationAttemptStatusResponse =
|
export type V2IntegrationOauthStatusResponse =
|
||||||
V2IntegrationAttemptStatusResponses[keyof V2IntegrationAttemptStatusResponses]
|
V2IntegrationOauthStatusResponses[keyof V2IntegrationOauthStatusResponses]
|
||||||
|
|
||||||
export type V2IntegrationAttemptCompleteData = {
|
export type V2IntegrationOauthCompleteData = {
|
||||||
body: {
|
body: {
|
||||||
code?: string | null
|
code?: string | null
|
||||||
}
|
}
|
||||||
path: {
|
path: {
|
||||||
|
integrationID: string
|
||||||
attemptID: string
|
attemptID: string
|
||||||
}
|
}
|
||||||
query?: {
|
query?: {
|
||||||
@@ -16982,10 +16985,10 @@ export type V2IntegrationAttemptCompleteData = {
|
|||||||
workspace?: string | null
|
workspace?: string | null
|
||||||
} | null
|
} | null
|
||||||
}
|
}
|
||||||
url: "/api/integration/attempt/{attemptID}/complete"
|
url: "/api/integration/{integrationID}/connect/oauth/{attemptID}/complete"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type V2IntegrationAttemptCompleteErrors = {
|
export type V2IntegrationOauthCompleteErrors = {
|
||||||
/**
|
/**
|
||||||
* InvalidRequestError
|
* InvalidRequestError
|
||||||
*/
|
*/
|
||||||
@@ -16996,18 +16999,17 @@ export type V2IntegrationAttemptCompleteErrors = {
|
|||||||
401: UnauthorizedError
|
401: UnauthorizedError
|
||||||
}
|
}
|
||||||
|
|
||||||
export type V2IntegrationAttemptCompleteError =
|
export type V2IntegrationOauthCompleteError = V2IntegrationOauthCompleteErrors[keyof V2IntegrationOauthCompleteErrors]
|
||||||
V2IntegrationAttemptCompleteErrors[keyof V2IntegrationAttemptCompleteErrors]
|
|
||||||
|
|
||||||
export type V2IntegrationAttemptCompleteResponses = {
|
export type V2IntegrationOauthCompleteResponses = {
|
||||||
/**
|
/**
|
||||||
* <No Content>
|
* <No Content>
|
||||||
*/
|
*/
|
||||||
204: void
|
204: void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type V2IntegrationAttemptCompleteResponse =
|
export type V2IntegrationOauthCompleteResponse =
|
||||||
V2IntegrationAttemptCompleteResponses[keyof V2IntegrationAttemptCompleteResponses]
|
V2IntegrationOauthCompleteResponses[keyof V2IntegrationOauthCompleteResponses]
|
||||||
|
|
||||||
export type V2McpListData = {
|
export type V2McpListData = {
|
||||||
body?: never
|
body?: never
|
||||||
|
|||||||
@@ -48,12 +48,12 @@ export const IntegrationHandler = HttpApiBuilder.group(Api, "server.integration"
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.handle(
|
.handle(
|
||||||
"integration.connect.oauth",
|
"integration.oauth.connect",
|
||||||
Effect.fn(function* (ctx) {
|
Effect.fn(function* (ctx) {
|
||||||
const service = yield* Integration.Service
|
const service = yield* Integration.Service
|
||||||
return yield* response(
|
return yield* response(
|
||||||
authorize(
|
authorize(
|
||||||
service.connection.oauth({
|
service.oauth.connect({
|
||||||
integrationID: ctx.params.integrationID,
|
integrationID: ctx.params.integrationID,
|
||||||
methodID: ctx.payload.methodID,
|
methodID: ctx.payload.methodID,
|
||||||
inputs: ctx.payload.inputs,
|
inputs: ctx.payload.inputs,
|
||||||
@@ -64,39 +64,53 @@ export const IntegrationHandler = HttpApiBuilder.group(Api, "server.integration"
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.handle(
|
.handle(
|
||||||
"integration.attempt.status",
|
"integration.oauth.status",
|
||||||
Effect.fn(function* (ctx) {
|
Effect.fn(function* (ctx) {
|
||||||
const service = yield* Integration.Service
|
const service = yield* Integration.Service
|
||||||
return yield* response(service.attempt.status(ctx.params.attemptID))
|
return yield* response(
|
||||||
|
service.oauth.status({
|
||||||
|
integrationID: ctx.params.integrationID,
|
||||||
|
attemptID: ctx.params.attemptID,
|
||||||
|
}),
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.handle(
|
.handle(
|
||||||
"integration.attempt.complete",
|
"integration.oauth.complete",
|
||||||
Effect.fn(function* (ctx) {
|
Effect.fn(function* (ctx) {
|
||||||
const service = yield* Integration.Service
|
const service = yield* Integration.Service
|
||||||
yield* service.attempt.complete({ attemptID: ctx.params.attemptID, code: ctx.payload.code }).pipe(
|
yield* service.oauth
|
||||||
Effect.mapError(
|
.complete({
|
||||||
(error) =>
|
integrationID: ctx.params.integrationID,
|
||||||
new InvalidRequestError({
|
attemptID: ctx.params.attemptID,
|
||||||
message:
|
code: ctx.payload.code,
|
||||||
error._tag === "Integration.CodeRequired"
|
})
|
||||||
? "Authorization code is required"
|
.pipe(
|
||||||
: "Authentication failed",
|
Effect.mapError(
|
||||||
kind:
|
(error) =>
|
||||||
error._tag === "Integration.CodeRequired"
|
new InvalidRequestError({
|
||||||
? "integration_code_required"
|
message:
|
||||||
: "integration_authorization",
|
error._tag === "Integration.CodeRequired"
|
||||||
}),
|
? "Authorization code is required"
|
||||||
),
|
: "Authentication failed",
|
||||||
)
|
kind:
|
||||||
|
error._tag === "Integration.CodeRequired"
|
||||||
|
? "integration_code_required"
|
||||||
|
: "integration_authorization",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
return HttpApiSchema.NoContent.make()
|
return HttpApiSchema.NoContent.make()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.handle(
|
.handle(
|
||||||
"integration.attempt.cancel",
|
"integration.oauth.cancel",
|
||||||
Effect.fn(function* (ctx) {
|
Effect.fn(function* (ctx) {
|
||||||
const service = yield* Integration.Service
|
const service = yield* Integration.Service
|
||||||
yield* service.attempt.cancel(ctx.params.attemptID)
|
yield* service.oauth.cancel({
|
||||||
|
integrationID: ctx.params.integrationID,
|
||||||
|
attemptID: ctx.params.attemptID,
|
||||||
|
})
|
||||||
return HttpApiSchema.NoContent.make()
|
return HttpApiSchema.NoContent.make()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { TextAttributes } from "@opentui/core"
|
import { TextAttributes } from "@opentui/core"
|
||||||
import type {
|
import type {
|
||||||
ConnectionInfo,
|
ConnectionInfo,
|
||||||
IntegrationConnectOauthOutput,
|
|
||||||
IntegrationInfo,
|
IntegrationInfo,
|
||||||
|
IntegrationOauthConnectOutput,
|
||||||
IntegrationOAuthMethod,
|
IntegrationOAuthMethod,
|
||||||
} from "@opencode-ai/client"
|
} from "@opencode-ai/client"
|
||||||
import { createMemo, createSignal, onCleanup, onMount, Show } from "solid-js"
|
import { createMemo, createSignal, onCleanup, onMount, Show } from "solid-js"
|
||||||
@@ -27,7 +27,7 @@ const INTEGRATION_PRIORITY: Record<string, number> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ConnectMethod = Exclude<IntegrationInfo["methods"][number], { type: "env" }>
|
type ConnectMethod = Exclude<IntegrationInfo["methods"][number], { type: "env" }>
|
||||||
type IntegrationAttempt = IntegrationConnectOauthOutput["data"]
|
type IntegrationAttempt = IntegrationOauthConnectOutput["data"]
|
||||||
type OnIntegrationConnected = (providerID?: string) => void
|
type OnIntegrationConnected = (providerID?: string) => void
|
||||||
|
|
||||||
export function integrationOptions(list: IntegrationInfo[]) {
|
export function integrationOptions(list: IntegrationInfo[]) {
|
||||||
@@ -227,8 +227,8 @@ function OAuthStarting(props: {
|
|||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
void client.api.integration.connect
|
void client.api.integration.oauth
|
||||||
.oauth({
|
.connect({
|
||||||
integrationID: props.integration.id,
|
integrationID: props.integration.id,
|
||||||
location: location(data),
|
location: location(data),
|
||||||
methodID: props.method.id,
|
methodID: props.method.id,
|
||||||
@@ -297,8 +297,8 @@ function OAuthAuto(props: {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
const poll = () => {
|
const poll = () => {
|
||||||
void client.api.integration.attempt
|
void client.api.integration.oauth
|
||||||
.status({ attemptID: props.attempt.attemptID, location: location(data) })
|
.status({ integrationID: props.integration.id, attemptID: props.attempt.attemptID, location: location(data) })
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
const status = result.data
|
const status = result.data
|
||||||
if (status.status === "pending") {
|
if (status.status === "pending") {
|
||||||
@@ -324,7 +324,11 @@ function OAuthAuto(props: {
|
|||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
if (timer) clearTimeout(timer)
|
if (timer) clearTimeout(timer)
|
||||||
if (settled) return
|
if (settled) return
|
||||||
void client.api.integration.attempt.cancel({ attemptID: props.attempt.attemptID, location: location(data) })
|
void client.api.integration.oauth.cancel({
|
||||||
|
integrationID: props.integration.id,
|
||||||
|
attemptID: props.attempt.attemptID,
|
||||||
|
location: location(data),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -354,7 +358,11 @@ function OAuthCode(props: {
|
|||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
if (settled) return
|
if (settled) return
|
||||||
void client.api.integration.attempt.cancel({ attemptID: props.attempt.attemptID, location: location(data) })
|
void client.api.integration.oauth.cancel({
|
||||||
|
integrationID: props.integration.id,
|
||||||
|
attemptID: props.attempt.attemptID,
|
||||||
|
location: location(data),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -363,8 +371,13 @@ function OAuthCode(props: {
|
|||||||
placeholder="Authorization code"
|
placeholder="Authorization code"
|
||||||
onConfirm={(code) => {
|
onConfirm={(code) => {
|
||||||
if (!code) return
|
if (!code) return
|
||||||
void client.api.integration.attempt
|
void client.api.integration.oauth
|
||||||
.complete({ attemptID: props.attempt.attemptID, location: location(data), code })
|
.complete({
|
||||||
|
integrationID: props.integration.id,
|
||||||
|
attemptID: props.attempt.attemptID,
|
||||||
|
location: location(data),
|
||||||
|
code,
|
||||||
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
settled = true
|
settled = true
|
||||||
return connected(props.integration, data, dialog, toast, props.onConnected)
|
return connected(props.integration, data, dialog, toast, props.onConnected)
|
||||||
|
|||||||
Reference in New Issue
Block a user