-
Notifications
You must be signed in to change notification settings - Fork 77
feat: add inline actions to agent-sdk #1547
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
base: main
Are you sure you want to change the base?
Conversation
|
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
Add inline actions to agent-sdk and emit
actionsandintentevents inAgentwith codecsActionsCodecandIntentCodecIntroduce a public
./inline-actionsexport, add codecs and content types for actions and intent, wire message filters andAgentevent dispatch, and provide aninlineActionsMiddlewareplus builder/utilities for sending and handling action menus.📍Where to Start
Start with codec definitions in
types/ActionsContent.tsandtypes/IntentContent.ts, then follow integration insrc/core/Agent.tsand predicates insrc/core/filter.ts.📊 Macroscope summarized ff73769. 6 files reviewed, 26 issues evaluated, 25 issues filtered, 0 comments posted
🗂️ Filtered Issues
sdks/agent-sdk/src/content-types/inline-actions/inline-actions.ts — 0 comments posted, 15 evaluated, 14 filtered
lastSentActionMessageis a module-global mutable variable used to track the "last" message across executions. In any environment with concurrent or interleaved requests, this can leak state across sessions/users and produce race conditions: the value may be overwritten by a different request before a consumer reads it, or a consumer may read a stale value from an earlier request. This violates at-most-once and provenance invariants for "last" and can cause incorrect behavior in multi-context usage. [ Already posted ]lastSentActionMessagecan retain a reference to arbitraryunknownobjects indefinitely at module scope, creating memory retention across requests. Without explicit per-context cleanup on all exit paths (success, error, cancellation), large messages or cyclic graphs may be kept alive longer than intended, causing memory growth/leaks until overwritten or the process restarts. [ Low confidence ]lastShownMenuis a module-global mutable variable tracking a{ config: AppConfig; menuId: string }. In concurrent or multi-session environments, this can leak a menu/config from one session into another and cause races where one request overwrites the "last" menu while another expects to re-show its own menu, resulting in wrong navigation for users and broken invariants. [ Already posted ]actionHandlersregistry allows silent handler overwrites based on duplicateactionIds across menus or features.registerActionwarns but proceeds to overwrite. DuringinitializeAppFromConfig, multiple menus can define actions with the sameid, or user-defined IDs may conflict with auto-registered ones like"main-menu","help","back-to-main". This can lead to incorrect handler execution at runtime when an intent arrives for a duplicated ID. [ Low confidence ]actionHandlersleads to a memory leak. Functions likesendConfirmationandsendSelectionregister per-interaction action IDs (e.g., usingDate.now()or caller-provided IDs) viaregisterAction, but there is no corresponding deregistration after the intent is processed. Over time, the globalactionHandlersMapaccumulates entries and is never cleared (except manually viaclearAllActions), causing memory usage to grow with user interactions. [ Low confidence ]lastSentActionMessageis shared across all conversations and invocations, causing races and cross-conversation/state bleed. Concurrent sends will overwrite the value, andgetLastSentActionMessage()returns whichever message was last sent globally rather than the one relevant to the currentctx. This violates at-most-once and uniqueness/association invariants and can surface the wrong message to callers. [ Low confidence ]showLastMenuclaims to "Fallback to main menu" in the comment, but when no last menu is tracked it only sends a text message"Returning to main menu..."and does not actually show the main menu or any actions. This contradicts the documented intent and yields a degraded UX where users cannot navigate. [ Low confidence ]inlineActionsMiddlewaredetects intents viactx.message.contentType?.typeId === "intent". This brittle check can miss valid intents or misclassify messages if the intent content type differs byauthorityIdor versioning semantics. The SDK typically compares viasameAs(ContentTypeIntent), which accounts for the full content type identifier. Using onlytypeIdrisks contract mismatches across versions. [ Low confidence ]ActionBuilder.addallows duplicateids within a singleActionsContentwithout validation. Inline actions typically require unique IDs per menu to correctly route intents. Duplicates create ambiguity and can silently misroute or ignore user selections, violating sequence uniqueness constraints. [ Low confidence ]lastSentActionMessageis set inActionBuilder.send, which will overwrite the global value on every send. Under concurrent or multi-conversation use, this causes races and wrong association when retrieved viagetLastSentActionMessage(). Each send should either return the message to the caller or store per-conversation/per-user state to avoid cross-talk. [ Low confidence ]sendActionsalso sets the same globallastSentActionMessage, producing the same cross-conversation/race issues. Multiple send paths compete to set this shared value unexpectedly. [ Low confidence ]input.trim()for validation but do not expose or return the normalized value. This can cause a mismatch where an input with leading/trailing whitespace (e.g.,' 0xabc...') is deemed valid, yet downstream code consuming the original untrimmed value may fail. To preserve invariants, either return the trimmed value alongsidevalid, or require callers to supply already-normalized input and avoid trimming here. [ Low confidence ]ethereumAddressvalidator accepts any0x+ 40 hex characters and does not enforce EIP-55 checksum casing. If downstream components require checksum addresses, this validator will incorrectly mark non-checksummed addresses as valid, enabling malformed inputs to pass validation. [ Low confidence ]showNavigationOptionscan send anActionsContentwith zeroactionsif neithercustomActionsis provided nor amain-menuexists inconfig. This produces an unusable navigation message and may violate the target contract if empty action lists are not allowed, yielding a poor or invalid user artifact without fallback. [ Low confidence ]sdks/agent-sdk/src/content-types/inline-actions/types/ActionsContent.ts — 0 comments posted, 6 evaluated, 6 filtered
decodeblindly readscontent.parameters.encodingwithout checking thatcontent.parametersexists. Ifparametersis undefined (which is possible forEncodedContentproduced by other codecs or sources), this causes a runtimeTypeError: Cannot read properties of undefined (reading 'encoding'). Add a guard, e.g.,const encoding = content.parameters?.encoding;. [ Low confidence ]decodeonly accepts exactly"UTF-8"for theencodingparameter and throws otherwise. Other producers may emit variants like"utf-8","UTF8", or omit the parameter. The strict case-sensitive check can cause unnecessary runtime errors and interoperability issues. Normalizing case or accepting common aliases would avoid failures. [ Low confidence ]validateContentrejectsaction.style === "secondary"even though theAction.styletype includes"secondary". The checkif (action.style && !["primary", "danger"].includes(action.style))omits"secondary", and the error message itself claims allowed values areprimary, secondary, danger. This will throw on valid inputs and contradicts the declared type and message. [ Already posted ]validateContentrejects the validAction.stylevalue"secondary". The typeActiondeclaresstyle?: "primary" | "secondary" | "danger", and the error message says it must be one ofprimary, secondary, danger, but the check atvalidateContentonly allows["primary", "danger"]. This will cause a runtime error whenstyleis"secondary". Update the inclusion check to allow"secondary"as declared. [ Already posted ]isValidISO8601is overly strict: it createsnew Date(timestamp)and requiresdate.toISOString() === timestamp. This rejects many valid ISO-8601 strings (e.g., those with timezone offsets+01:00, without milliseconds, or lowercasez). As a result, validexpiresAtvalues for actions and content are incorrectly rejected, causing unnecessary runtime errors invalidateContent. Consider a real ISO-8601 parser or a more permissive check. [ Already posted ]isValidISO8601usesnew Date(timestamp)and comparesdate.toISOString() === timestamp. This is overly strict and will reject many valid ISO-8601/RFC 3339 timestamps (e.g., those without milliseconds like"2023-08-01T12:00:00Z", or with timezone offsets like"2023-08-01T12:00:00+02:00"). Given the function is labeled "Basic ISO-8601 timestamp validation", this strict equality causes avoidable runtime failures on valid inputs. Consider a more permissive validation (e.g., parsing and checking for validity without requiring exacttoISOString()equivalence). [ Already posted ]sdks/agent-sdk/src/content-types/inline-actions/types/IntentContent.ts — 0 comments posted, 3 evaluated, 3 filtered
decodedoes not verify thatcontent.typematchesContentTypeIntentbefore decoding. If a mismatched content type is passed, it may produce confusing errors or incorrectly accept malformed data. Add a type guard to ensure onlyContentTypeIntentis decoded. [ Low confidence ]decodedereferencescontent.parameterswithout a null/undefined guard:const encoding = content.parameters.encoding;. Ifparametersis absent (which is allowed inEncodedContent), this throws at runtime. Add a safe access or default before readingencoding. [ Low confidence ]decodeenforces a case-sensitive check forencodingequal to"UTF-8"and rejects other common variants like"utf-8". This can cause valid content from other senders to be rejected unnecessarily. Normalize or case-insensitively compare theencodingvalue. [ Low confidence ]sdks/agent-sdk/src/core/Agent.ts — 0 comments posted, 2 evaluated, 2 filtered
start()sets#isLocked = trueat entry and, on failure, callsthis.#handleStreamError(error)at line 436. If the error chain does not indicate recovery (recovered === falseinside#handleStreamError), the lock is never cleared and no restart is scheduled, leaving the agent permanently locked with no active streams. Subsequentstart()calls will early-return due to#isLockedbeingtrue. The lock must be cleared on all non-recovery paths orstop()invoked to restore a usable state. [ Out of scope ]start()fails and delegates to#handleStreamErrorat line 436, the automatic restart path inside#handleStreamErrorusesqueueMicrotask(() => this.start())without forwarding the originalAgentStreamingOptions. This silently drops caller-provided streaming options on recovery, changing externally visible behavior (contract parity) after an initialization error. [ Out of scope ]