Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
fix(core): align ChatGPT token sharing with updated partner guide
  • Loading branch information
rekram1-node committed Sep 29, 2026
commit 23a712d3b1d2a0751597e4852c45d6464a36864b
15 changes: 10 additions & 5 deletions packages/core/src/plugin/provider/chatgpt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ const fallbackModels = new Set([
])
const nonRetryableSharingCodes = [
"subscription_sharing_usage_limit_exceeded",
"subscription_sharing_v2_user_not_eligible",
"subscription_sharing_user_not_eligible",
"subscription_sharing_unsupported_capability",
"subscription_sharing_v2_client_not_enabled",
"subscription_sharing_v2_route_not_supported",
"subscription_sharing_v2_invalid_user",
"subscription_sharing_route_not_supported",
"subscription_sharing_invalid_user",
"chatpass_v2_scope_not_authorized",
"chatpass_v2_invalid_authorization_context",
]
type Pkce = {
verifier: string
Expand Down Expand Up @@ -155,7 +156,11 @@ const signIn = (app: App.Info, savedClientID: () => string | undefined, storage:
)
return yield* Effect.gen(function* () {
// Reauthorization callbacks may omit the client ID; reuse the one this attempt started with.
const clientID = result.clientID ?? savedID
if (savedID && result.clientID && result.clientID !== savedID)
return yield* Effect.fail(
new Error("ChatGPT returned a different client than this connection. Connect again."),
)
const clientID = savedID ?? result.clientID
if (!clientID)
return yield* Effect.fail(new Error("ChatGPT sign-in did not return a client ID. Connect again."))
const tokens = yield* exchange(result.code, clientID, redirect, pkce, app)
Expand Down
14 changes: 8 additions & 6 deletions packages/core/src/session/to-session-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,21 @@ import { ModelResolver } from "../model-resolver.js"
import { SessionRunnerModel } from "./runner/model.js"

const tokenSharingMessages = {
subscription_sharing_v2_user_not_eligible:
subscription_sharing_user_not_eligible:
"ChatGPT token sharing isn't available for this account. Connect with an API key or choose another provider.",
subscription_sharing_usage_limit_exceeded:
"ChatGPT usage limit reached. Try again after your allowance resets; check ChatGPT Settings → Usage for details.",
subscription_sharing_usage_unavailable: "ChatGPT usage can't be checked right now. Try again later.",
subscription_sharing_unsupported_capability:
"This request uses a feature ChatGPT token sharing doesn't support. Remove the unsupported feature and try again.",
subscription_sharing_v2_client_not_enabled:
"This ChatGPT client isn't enabled for token sharing. Contact the app maintainer or choose another connection.",
subscription_sharing_v2_route_not_supported:
subscription_sharing_route_not_supported:
"ChatGPT token sharing doesn't support this API route. Check the configured endpoint and HTTP method.",
subscription_sharing_v2_invalid_user: "This ChatGPT connection is no longer valid. Reconnect to ChatGPT.",
subscription_sharing_v2_user_unavailable: "Your ChatGPT account is temporarily unavailable. Try again later.",
subscription_sharing_invalid_user: "This ChatGPT connection is no longer valid. Reconnect to ChatGPT.",
subscription_sharing_user_unavailable: "Your ChatGPT account is temporarily unavailable. Try again later.",
chatpass_v2_scope_not_authorized:
"This ChatGPT connection isn't authorized for this request. Reconnect to ChatGPT or choose another connection.",
chatpass_v2_invalid_authorization_context:
"This ChatGPT connection isn't authorized for this request. Reconnect to ChatGPT or choose another connection.",
}

export function toSessionError(cause: unknown): SessionError.Error {
Expand Down
44 changes: 39 additions & 5 deletions packages/core/test/plugin/provider-chatgpt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,13 +356,14 @@ describe("ChatGPTPlugin", () => {
const hooks = yield* PluginHooks.Service
const decide = yield* SessionRunnerRetry.policy(Session.ID.make("ses_sharing_errors"))
const cases = [
["subscription_sharing_v2_user_not_eligible", false],
["subscription_sharing_user_not_eligible", false],
["subscription_sharing_unsupported_capability", false],
["subscription_sharing_v2_client_not_enabled", false],
["subscription_sharing_v2_route_not_supported", false],
["subscription_sharing_v2_invalid_user", false],
["subscription_sharing_route_not_supported", false],
["subscription_sharing_invalid_user", false],
["chatpass_v2_scope_not_authorized", false],
["chatpass_v2_invalid_authorization_context", false],
["subscription_sharing_usage_unavailable", true],
["subscription_sharing_v2_user_unavailable", true],
["subscription_sharing_user_unavailable", true],
] as const
for (const [code, retry] of cases) {
const cause = new AIError({
Expand Down Expand Up @@ -458,6 +459,39 @@ describe("ChatGPTPlugin", () => {
}),
)

it.live("rejects a reauthorization callback that returns a different client ID", () =>
Effect.gen(function* () {
const credentials = yield* Credential.Service
yield* credentials.create({
integrationID: Integration.ID.make("openai"),
value: Credential.OAuth.make({
type: "oauth",
methodID: Integration.MethodID.make("chatgpt-token-sharing"),
access: "chatgpt-token",
refresh: "refresh",
expires: Date.now() + 60 * 60_000,
metadata: { clientID: "oaiapp_issued" },
}),
})
yield* addPlugin()
const integrations = yield* Integration.Service
const attempt = yield* integrations.oauth.connect({
integrationID: Integration.ID.make("openai"),
methodID: Integration.MethodID.make("chatgpt-token-sharing"),
})
const authorization = new URL(attempt.url)
expect(authorization.searchParams.get("client_id")).toBe("oaiapp_issued")
const callback = new URL(authorization.searchParams.get("redirect_uri") ?? "")
callback.searchParams.set("code", "test-code")
callback.searchParams.set("state", authorization.searchParams.get("state") ?? "")
callback.searchParams.set("client_id", "oaiapp_other")
const response = yield* Effect.promise(() => fetch(callback))
expect(response.status).toBe(400)
expect(yield* Effect.promise(() => response.text())).toContain("different client")
expect(yield* credentials.list(Integration.ID.make("openai"))).toHaveLength(1)
}),
)

it.effect("moves only token-sharing credentials to the new method ID and caches their saved models", () =>
Effect.gen(function* () {
const credentials = yield* Credential.Service
Expand Down
11 changes: 6 additions & 5 deletions packages/core/test/session-error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,15 @@ describe("toSessionError", () => {

test("maps token-sharing failures while preserving HTTP and stream bodies", () => {
const cases = [
["subscription_sharing_v2_user_not_eligible", "token sharing isn't available for this account"],
["subscription_sharing_user_not_eligible", "token sharing isn't available for this account"],
["subscription_sharing_usage_limit_exceeded", "ChatGPT usage limit reached"],
["subscription_sharing_usage_unavailable", "Try again later"],
["subscription_sharing_unsupported_capability", "Remove the unsupported feature"],
["subscription_sharing_v2_client_not_enabled", "Contact the app maintainer"],
["subscription_sharing_v2_route_not_supported", "Check the configured endpoint"],
["subscription_sharing_v2_invalid_user", "Reconnect to ChatGPT"],
["subscription_sharing_v2_user_unavailable", "Try again later"],
["subscription_sharing_route_not_supported", "Check the configured endpoint"],
["subscription_sharing_invalid_user", "Reconnect to ChatGPT"],
["subscription_sharing_user_unavailable", "Try again later"],
["chatpass_v2_scope_not_authorized", "isn't authorized for this request"],
["chatpass_v2_invalid_authorization_context", "isn't authorized for this request"],
] as const
for (const [code, guidance] of cases) {
for (const body of [
Expand Down
Loading