-
Notifications
You must be signed in to change notification settings - Fork 55
feat: Add linkClaimAttemptToExternalUser to Agents module #1652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,10 @@ import { WorkOS } from '../workos'; | |
| import { | ||
| AgentCredentialValidation, | ||
| AgentRegistration, | ||
| ClaimAttemptResponse, | ||
| LinkClaimAttemptToExternalUserOptions, | ||
| SerializedAgentAccessTokenClaims, | ||
| SerializedClaimAttemptResponse, | ||
| SerializedAgentCredentialValidation, | ||
| SerializedAgentRegistration, | ||
| ValidateAgentAccessTokenOptions, | ||
|
|
@@ -13,6 +16,8 @@ import { | |
| deserializeAgentAccessTokenClaims, | ||
| deserializeAgentCredentialValidation, | ||
| deserializeAgentRegistration, | ||
| deserializeClaimAttemptResponse, | ||
| serializeLinkClaimAttemptToExternalUserOptions, | ||
| serializeValidateAgentCredentialOptions, | ||
| } from './serializers'; | ||
|
|
||
|
|
@@ -41,6 +46,32 @@ export class Agents { | |
|
|
||
| constructor(private readonly workos: WorkOS) {} | ||
|
|
||
| /** | ||
| * Link a claim attempt to an external user | ||
| * | ||
| * Link an external user to a claim attempt and retrieve the code needed | ||
| * for the agent to complete the claim. The user is looked up by external | ||
| * ID; if no user exists, one is created. When the user belongs to multiple | ||
| * organizations, an explicit organization must be provided. | ||
| * | ||
| * @param options - Object containing the claim attempt token, user details, and optional organization ID. | ||
| * @returns {Promise<ClaimAttemptResponse>} | ||
| * @throws {BadRequestException} 400 - Invalid request, email mismatch, or wrong account. | ||
| * @throws {ForbiddenException} 403 - Claim denied or auth method disabled. | ||
| * @throws {ConflictException} 409 - Organization selection required, external ID conflict, or already claimed. | ||
| * @throws {GoneException} 410 - Claim or user code expired. | ||
| */ | ||
| async linkClaimAttemptToExternalUser( | ||
| options: LinkClaimAttemptToExternalUserOptions, | ||
| ): Promise<ClaimAttemptResponse> { | ||
|
m0tzy marked this conversation as resolved.
|
||
| const { data } = await this.workos.patch<SerializedClaimAttemptResponse>( | ||
| '/agents/claims/attempts', | ||
| serializeLinkClaimAttemptToExternalUserOptions(options), | ||
| ); | ||
|
Comment on lines
+67
to
+70
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The PR description explicitly states this wraps Prompt To Fix With AIThis is a comment left during a code review.
Path: src/agents/agents.ts
Line: 67-70
Comment:
**HTTP method contradicts the PR description**
The PR description explicitly states this wraps `POST /agents/claims/attempts`, but the implementation calls `this.workos.patch(...)`. If the WorkOS API endpoint only accepts `POST`, every call to `linkClaimAttemptToExternalUser` will fail with a `405 Method Not Allowed` response. The tests assert `'PATCH'` which keeps them consistent with the implementation, but they won't catch this kind of server-side rejection. Please confirm the intended HTTP verb against the API spec and align the implementation, tests, and PR description accordingly.
How can I resolve this? If you propose a fix, please make it concise.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| return deserializeClaimAttemptResponse(data); | ||
| } | ||
|
|
||
| /** | ||
| * Get an agent registration | ||
| * | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "id": "agent_reg_01EHZNVPK3SFK441A1RGBFSHRT", | ||
| "status": "unverified", | ||
| "user_code": "BCDF-GHJK", | ||
| "organizations": [ | ||
| { | ||
| "id": "org_01EHZNVPK3SFK441A1RGBFSHRT", | ||
| "name": "Acme Corp" | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import { AgentRegistrationStatus } from './agent-registration.interface'; | ||
|
|
||
| /** Options for linking an external user to a claim attempt via the admin API. */ | ||
| export interface LinkClaimAttemptToExternalUserOptions { | ||
| /** The claim attempt token identifying the pending claim. */ | ||
| claimAttemptToken: string; | ||
| /** The user to attach to the claim attempt. */ | ||
| user: { | ||
| /** The email address of the user. */ | ||
| email: string; | ||
| /** The external ID of the user. */ | ||
| externalId: string; | ||
| }; | ||
| /** The organization to place the agent in. Required when the user belongs to multiple organizations. */ | ||
| organizationId?: string; | ||
| } | ||
|
|
||
| export interface SerializedLinkClaimAttemptToExternalUserOptions { | ||
| type: 'link_external_user'; | ||
| claim_attempt_token: string; | ||
| user: { | ||
| email: string; | ||
| external_id: string; | ||
| }; | ||
| organization_id?: string; | ||
| } | ||
|
|
||
| /** An organization the confirming user belongs to, offered as a placement choice. */ | ||
| export interface ClaimAttemptOrganization { | ||
| /** The organization ID. */ | ||
| id: string; | ||
| /** The organization name. */ | ||
| name: string; | ||
| } | ||
|
|
||
| /** The result of linking an external user to a claim attempt. */ | ||
| export interface ClaimAttemptResponse { | ||
| /** The agent registration ID. */ | ||
| id: string; | ||
| /** Current status of the agent registration. */ | ||
| status: AgentRegistrationStatus; | ||
| /** The user code the agent needs to complete the claim. */ | ||
| userCode: string; | ||
| /** Organizations the user belongs to, offered as placement choices. */ | ||
| organizations: ClaimAttemptOrganization[]; | ||
| } | ||
|
|
||
| export interface SerializedClaimAttemptResponse { | ||
| id: string; | ||
| status: AgentRegistrationStatus; | ||
| user_code: string; | ||
| organizations: ClaimAttemptOrganization[]; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| export * from './agent-registration.interface'; | ||
| export * from './claim-attempt.interface'; | ||
| export * from './validate-agent-credential.interface'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import { | ||
| ClaimAttemptResponse, | ||
| LinkClaimAttemptToExternalUserOptions, | ||
| SerializedClaimAttemptResponse, | ||
| SerializedLinkClaimAttemptToExternalUserOptions, | ||
| } from '../interfaces/claim-attempt.interface'; | ||
|
|
||
| export function serializeLinkClaimAttemptToExternalUserOptions( | ||
| options: LinkClaimAttemptToExternalUserOptions, | ||
| ): SerializedLinkClaimAttemptToExternalUserOptions { | ||
| return { | ||
| type: 'link_external_user', | ||
| claim_attempt_token: options.claimAttemptToken, | ||
| user: { | ||
| email: options.user.email, | ||
| external_id: options.user.externalId, | ||
| }, | ||
| ...(options.organizationId !== undefined && { | ||
| organization_id: options.organizationId, | ||
| }), | ||
| }; | ||
| } | ||
|
|
||
| export function deserializeClaimAttemptResponse( | ||
| response: SerializedClaimAttemptResponse, | ||
| ): ClaimAttemptResponse { | ||
| return { | ||
| id: response.id, | ||
| status: response.status, | ||
| userCode: response.user_code, | ||
| organizations: response.organizations, | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| export * from './agent-registration.serializer'; | ||
| export * from './claim-attempt.serializer'; | ||
| export * from './validate-agent-credential.serializer'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a strong opinion here on the name, but just flagging that this is different from the other SDKs' generated method (
createAttempt) so I'm wondering if we should rename this to something more similar or the same.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This name was a deliberate choice by @m0tzy earlier in the design of this endpoint. The autogenerated SDKs surface a generic
createAttempt(type=...)because they're driven directly off the OpenAPI operation, but for the hand-written Node method Madison wanted the name to describe what the operation actually does — link an external user to a claim attempt — rather than expose the lower-leveltypediscriminator. The corresponding docs/API summary was also updated to "Link a claim attempt to an external user" to match.Happy to rename to align with the generated SDKs (
createAttempt) if the team would rather keep them consistent — deferring to @m0tzy on the final call since it was a product decision.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update on this: we resolved the divergence by switching the endpoint from
POSTtoPATCH /agents/claims/attempts(it modifies an existing claim attempt rather than creating one — the agent already created it). The generated SDKs now surface a genericupdateAttempts({ type: 'link_external_user', ... }), where thetypearg disambiguates the operation. This hand-written Node SDK keeps the ergonomiclinkClaimAttemptToExternalUser(...)wrapper over the same PATCH call (it hardcodestypeinternally). So the remaining name difference is intentional — hand-written SDKs are allowed to be friendlier than the generated ones. Server + docs are updated in workos/workos#64535.