Skip to content

Commit 1470cc7

Browse files
MarkDaoustcopybara-github
authored andcommitted
feat(interactions): add automaticContinuation to Interactions.CreateAsync in the .NET SDK
Foreground `Interactions.CreateAsync()` calls are capped per request by the server (32k output tokens unary, 64k streaming) and stop with `status="requires_continuation"` plus a `continuation_token`. `IInteractions.CreateAsync` and `Interactions.CreateAsync` now accept `bool? automaticContinuation = null` (default `true`) and `TimeSpan? timeout = null` and chain continuation hops client-side so a plain `CreateAsync()` returns the full decode. Request chaining: - Each hop after the first sends the `continuation_token` from the previous hop. With `Store` enabled (default) it sets `PreviousInteractionId` to the hop that issued the token and omits `Input`; with `Store = false` it resends the original `Input`. - Unary foreground model calls default `GenerationConfig.MaxOutputTokens` to 65536 when unset without mutating caller request objects (streaming uses the server's 64k default). Explicit values are never overridden. - Stops on `completed`, `incomplete` (token preserved for manual resumption), `requires_action`, `failed`, `cancelled`, a missing token, or a repeated token. Skipped for agent requests and `Background = true`; `Background = true` with `automaticContinuation: false` throws `ArgumentException`. Unary (`Stream = false`) merging: - Hops are merged into one `Interaction`: `Steps` concatenated in hop order (leading `user_input` steps dropped on later hops), `Usage` counts and per-modality breakdowns summed across hops with `Usage.InitialInputTokens` taken from hop 1, `Id` / `Status` / `ContinuationToken` / `Updated` from the final hop, and `PreviousInteractionId` / `Created` / `Input` from hop 1. - `ContinuationIds` lists every hop id in order, de-duplicated with any server-provided ids. Convenience output properties (`OutputText`, etc.) are recomputed across hop boundaries. Streaming (`Stream = true`): - Events from all hops are yielded verbatim on `EventStream<InteractionSseEvent>` (`await foreach` and `Next()`). Consumers should treat `interaction.completed` with `status="requires_continuation"` as intermediate. - Mid-hop transport drops or premature EOF resume from the latest `interaction.status_update` checkpoint token when no content was yielded since that checkpoint (up to 3 no-progress reconnects). SSE `error` events are terminal; HTTP status retries stay with `RetryConfig`. Timeouts and errors: - Per-call `TimeSpan? timeout` bounds the whole `CreateAsync()` call across hops via `CallDeadline`, while `HttpClient.Timeout`, `RetryConfig`, and `CancellationToken` apply per hop / immediate cancellation. - A failure or timeout after progress throws `ContinuationInterruptedException` (`InnerException` set to the underlying error, or `ContinuationTimeoutException` on deadline expiry) carrying `PartialInteraction`, `ContinuationToken`, `InteractionId`, and `ContinuationIds` so billed work is preserved for manual resumption. First-hop failures propagate unchanged. Opt-out: - `automaticContinuation: false` returns the single server response untouched (no budget injection, no merging) and emits a one-time `Trace.TraceWarning` if it stops at `requires_continuation` without a caller-supplied `continuation_token`. Tests: `Google.GenAI.Tests/InteractionsContinuationConformanceTest.cs` (52 conformance cases) and `Google.GenAI.Tests/InteractionsContinuationTest.cs` (whole-call timeout with fake clock, non-mutation, warning, and `EventStream.Next()` tests). PiperOrigin-RevId: 995657094
1 parent cb44af3 commit 1470cc7

12 files changed

Lines changed: 2531 additions & 70 deletions

‎src/main/java/com/google/genai/gaos/AsyncInteractions.java‎

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,11 @@
3131
import com.google.genai.gaos.models.operations.async.CancelInteractionByIdRequestBuilder;
3232
import com.google.genai.gaos.models.operations.async.CancelInteractionByIdResponse;
3333
import com.google.genai.gaos.models.operations.async.CreateInteractionRequestBuilder;
34-
import com.google.genai.gaos.models.operations.async.CreateInteractionResponse;
3534
import com.google.genai.gaos.models.operations.async.DeleteInteractionRequestBuilder;
3635
import com.google.genai.gaos.models.operations.async.DeleteInteractionResponse;
3736
import com.google.genai.gaos.models.operations.async.GetInteractionByIdRequestBuilder;
3837
import com.google.genai.gaos.models.operations.async.GetInteractionByIdResponse;
3938
import com.google.genai.gaos.operations.CancelInteractionById;
40-
import com.google.genai.gaos.operations.CreateInteraction;
4139
import com.google.genai.gaos.operations.DeleteInteraction;
4240
import com.google.genai.gaos.operations.GetInteractionById;
4341
import com.google.genai.gaos.operations.Operations;
@@ -91,6 +89,20 @@ public CompletableFuture<EventStream<InteractionSSEStreamEvent>> create(@Nonnull
9189
return create(null, body, null);
9290
}
9391

92+
/**
93+
* Creates a new interaction.
94+
*
95+
* @param body The request body.
96+
* @param automaticContinuation Whether to automatically continue decoding across
97+
* continuation hops; see {@link #create(CreateInteractionRequest, Options)}.
98+
* @return {@code CompletableFuture<EventStream<InteractionSSEStreamEvent>}&gt; - The async response
99+
*/
100+
public CompletableFuture<EventStream<InteractionSSEStreamEvent>> create(
101+
@Nonnull CreateInteractionRequestBody body, boolean automaticContinuation) {
102+
return create(
103+
new CreateInteractionRequest(null, body, automaticContinuation, null), null);
104+
}
105+
94106
/**
95107
* Creates a new interaction.
96108
*
@@ -103,19 +115,42 @@ public CompletableFuture<EventStream<InteractionSSEStreamEvent>> create(@Nonnull
103115
public CompletableFuture<EventStream<InteractionSSEStreamEvent>> create(
104116
@Nullable String apiVersion, @Nonnull CreateInteractionRequestBody body,
105117
@Nullable Options options) {
106-
CreateInteractionRequest request = new CreateInteractionRequest(apiVersion, body);
107-
AsyncRequestOperation<CreateInteractionRequest, CreateInteractionResponse> operation
108-
= new CreateInteraction.Async(
109-
sdkConfiguration, options, sdkConfiguration.retryScheduler(),
110-
_headers);
111-
return Operations.relayCancel(Operations.applyBodyReadAsync(operation.doRequest(request),
112-
operation::handleResponse)
113-
.thenApplyAsync(response -> new EventStream<InteractionSSEStreamEvent>(
114-
response.rawResponse().body(),
115-
new TypeReference<InteractionSSEStreamEvent>() {
116-
},
117-
Utils.mapper(),
118-
Optional.of("[DONE]")), Operations.streamCompletionExecutor()), operation);
118+
return create(new CreateInteractionRequest(apiVersion, body), options);
119+
}
120+
121+
/**
122+
* Creates a new interaction.
123+
*
124+
* @param request The request object containing all the parameters for the API call.
125+
* @return A CompletableFuture that completes with a blocking event stream once response headers are received.
126+
* Iterating the stream blocks the calling thread; close it after use.
127+
*/
128+
public CompletableFuture<EventStream<InteractionSSEStreamEvent>> create(
129+
@Nonnull CreateInteractionRequest request) {
130+
return create(request, null);
131+
}
132+
133+
/**
134+
* Creates a new interaction.
135+
*
136+
* <p>Automatic continuation ({@code request.automaticContinuation()}, default {@code true})
137+
* works as described on {@link Interactions#create(CreateInteractionRequest, Options)}: with
138+
* {@code stream=true}, the events of every continuation hop are yielded sequentially and
139+
* verbatim on the returned stream, which fetches the next hop on the thread iterating it. A
140+
* failure after progress ends the iteration with a
141+
* {@link com.google.genai.gaos.models.errors.ContinuationInterruptedException} carrying the
142+
* token to resume from. {@code background=true} with {@code automaticContinuation(false)} is
143+
* rejected with an {@link IllegalArgumentException} before any request is sent.
144+
*
145+
* @param request The request object containing all the parameters for the API call.
146+
* @param options additional options
147+
* @return A CompletableFuture that completes with a blocking event stream once response headers are received.
148+
* Iterating the stream blocks the calling thread; close it after use.
149+
*/
150+
public CompletableFuture<EventStream<InteractionSSEStreamEvent>> create(
151+
@Nonnull CreateInteractionRequest request,
152+
@Nullable Options options) {
153+
return InteractionContinuation.createAsync(sdkConfiguration, request, options, _headers);
119154
}
120155

121156

0 commit comments

Comments
 (0)