You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by zh...@apache.org on 2026/08/31 19:00:54 UTC
(maka) 02/02: fix(runtime): complete form interaction adapter seam
This is an automated email from the ASF dual-hosted git repository.
me2seeks pushed a commit to branch feat/4364-runtime-form-interactions
in repository https://gitbox.apache.org/repos/asf/maka.git
commit d8bae9fffaa501b5fa0de383371ece11421bc3a2
Author: me2seeks <me...@gmail.com>
AuthorDate: Mon Aug 31 23:17:53 2026 +0800
fix(runtime): complete form interaction adapter seam
Expose one closed decoder for renderer-to-runtime form responses so surface adapters do not copy protocol validation. Queue the same canonical continuity refresh for form requests that user questions already receive.
Refs #4364.
Generated-by: OpenAI Codex
---
packages/core/src/__tests__/interaction.test.ts | 28 ++++++++++++++++++++++
packages/core/src/interaction.ts | 26 ++++++++++++++++++++
.../src/server/root-turn-coordinator.ts | 2 +-
3 files changed, 55 insertions(+), 1 deletion(-)
diff --git a/packages/core/src/__tests__/interaction.test.ts b/packages/core/src/__tests__/interaction.test.ts
index 868f6e5e01..ee012a4571 100644
--- a/packages/core/src/__tests__/interaction.test.ts
+++ b/packages/core/src/__tests__/interaction.test.ts
@@ -29,6 +29,7 @@ import {
InteractionPermissionProjectionError,
decodeInteractionAnswer,
decodeInteractionCanonicalOutcome,
+ decodeInteractionFormResponse,
decodeInteractionRequest,
interactionCanonicalOutcomesEquivalent,
isInteractionAnswerValidForRequest,
@@ -1256,4 +1257,31 @@ describe('Interaction decoding and validity', () => {
false,
);
});
+
+ test('decodes form responses as a closed renderer-to-runtime shape', () => {
+ assert.deepEqual(
+ decodeInteractionFormResponse({
+ requestId: 'form-1',
+ action: 'accept',
+ values: { replicas: 3, regions: ['us', 'eu'] },
+ }),
+ {
+ requestId: 'form-1',
+ action: 'accept',
+ values: { replicas: 3, regions: ['us', 'eu'] },
+ },
+ );
+ assert.deepEqual(decodeInteractionFormResponse({ requestId: 'form-1', action: 'decline' }), {
+ requestId: 'form-1',
+ action: 'decline',
+ });
+ for (const invalid of [
+ { requestId: 'form-1', action: 'accept' },
+ { requestId: 'form-1', action: 'cancel', values: {} },
+ { requestId: 'form-1', action: 'accept', values: {}, extra: true },
+ { requestId: '', action: 'decline' },
+ ]) {
+ assert.throws(() => decodeInteractionFormResponse(invalid));
+ }
+ });
});
diff --git a/packages/core/src/interaction.ts b/packages/core/src/interaction.ts
index 09dad93548..cbf2ce0af7 100644
--- a/packages/core/src/interaction.ts
+++ b/packages/core/src/interaction.ts
@@ -301,6 +301,12 @@ const FORM_ACCEPT_ANSWER_SHAPE = defineObjectShape<
const FORM_EMPTY_ANSWER_SHAPE = defineObjectShape<
Extract<InteractionFormAnswer, { action: 'decline' | 'cancel' }>
>()(['kind', 'action'], []);
+const FORM_ACCEPT_RESPONSE_SHAPE = defineObjectShape<
+ Extract<InteractionFormResponse, { action: 'accept' }>
+>()(['requestId', 'action', 'values'], []);
+const FORM_EMPTY_RESPONSE_SHAPE = defineObjectShape<
+ Extract<InteractionFormResponse, { action: 'decline' | 'cancel' }>
+>()(['requestId', 'action'], []);
const SANDBOX_BOUNDARY_ANSWER_SHAPE = defineObjectShape<InteractionSandboxBoundaryAnswer>()(
['kind', 'decision'],
[],
@@ -455,6 +461,26 @@ export function decodeInteractionAnswer(value: unknown): InteractionAnswer {
return deepFreeze(answer);
}
+export function decodeInteractionFormResponse(value: unknown): InteractionFormResponse {
+ const record = plainRecord(value, 'Interaction form response');
+ const requestId = boundedString(record.requestId, 'requestId', INTERACTION_ID_MAX_BYTES);
+ const action = oneOf(record.action, ['accept', 'decline', 'cancel'] as const, 'form action');
+ if (action === 'accept') {
+ exact(record, FORM_ACCEPT_RESPONSE_SHAPE, 'accepted form response');
+ const answer = decodeInteractionAnswer({
+ kind: 'form',
+ action,
+ values: record.values,
+ });
+ if (answer.kind !== 'form' || answer.action !== 'accept') {
+ throw new Error('Invalid accepted form response');
+ }
+ return deepFreeze({ requestId, action, values: answer.values });
+ }
+ exact(record, FORM_EMPTY_RESPONSE_SHAPE, 'empty form response');
+ return deepFreeze({ requestId, action });
+}
+
export function decodeInteractionCanonicalOutcome(value: unknown): InteractionCanonicalOutcome {
const record = plainRecord(value, 'Interaction canonical outcome');
let outcome: InteractionCanonicalOutcome;
diff --git a/packages/runtime-host/src/server/root-turn-coordinator.ts b/packages/runtime-host/src/server/root-turn-coordinator.ts
index 6de6170635..e6b8e00231 100644
--- a/packages/runtime-host/src/server/root-turn-coordinator.ts
+++ b/packages/runtime-host/src/server/root-turn-coordinator.ts
@@ -2388,7 +2388,7 @@ export class RootTurnCoordinator implements HostedExecutionAuthority {
await this.continuity.acceptRuntimeEvent(input.sessionId, active.runId, event);
} else if (isInteractionAnswerAck(event)) {
await this.continuity.refreshCanonical(input.sessionId);
- } else if (event.type === 'user_question_request') {
+ } else if (event.type === 'user_question_request' || event.type === 'form_request') {
this.continuity.enqueueCanonicalRefresh(input.sessionId);
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org