@sunerpy/opencode-kiro-auth 0.15.2 → 0.15.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/core/request/request-handler.js +4 -3
- package/dist/core/request/response-handler.d.ts +1 -0
- package/dist/core/request/response-handler.js +34 -10
- package/dist/plugin/config/loader.js +2 -0
- package/dist/plugin/config/schema.d.ts +15 -0
- package/dist/plugin/config/schema.js +13 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,6 +109,14 @@ multi-account or long-idle setups, enable
|
|
|
109
109
|
(`token_keepalive_enabled: true`) to keep idle accounts' tokens fresh while
|
|
110
110
|
OpenCode is running.
|
|
111
111
|
|
|
112
|
+
For long-running agent tasks that are frequently interrupted by upstream
|
|
113
|
+
`ECONNRESET` event-stream failures, enable
|
|
114
|
+
`"stream_buffer_until_complete": true`. The plugin then withholds a failed
|
|
115
|
+
attempt from OpenCode and safely retries it instead of exposing a partial
|
|
116
|
+
assistant response or partial tool call. See
|
|
117
|
+
[stream recovery configuration](docs/CONFIGURATION.md#options) for the latency
|
|
118
|
+
and quota tradeoffs.
|
|
119
|
+
|
|
112
120
|
Paid-overage protection is on by default; see
|
|
113
121
|
[Overage protection](docs/CONFIGURATION.md#overage-protection) before disabling
|
|
114
122
|
`stop_on_overage`.
|
|
@@ -231,6 +239,12 @@ accounts", `/connect` vs `opencode auth login`, and Kiro CLI OAuth users whose
|
|
|
231
239
|
sync doesn't start — are covered in
|
|
232
240
|
[docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md).
|
|
233
241
|
|
|
242
|
+
`Kiro upstream event stream failed unexpectedly` means the upstream HTTP 200
|
|
243
|
+
event stream ended before completion metadata. Live-stream mode cannot safely
|
|
244
|
+
replay after output because that could duplicate text or execute a tool twice.
|
|
245
|
+
Enable `stream_buffer_until_complete` when task continuity is more important
|
|
246
|
+
than seeing tokens arrive live.
|
|
247
|
+
|
|
234
248
|
## Migration
|
|
235
249
|
|
|
236
250
|
If you're upgrading from a version that used the provider id `kiro` instead of
|
|
@@ -14,7 +14,6 @@ import { RetryStrategy } from './retry-strategy.js';
|
|
|
14
14
|
import { SdkEventStreamIterationError, UpstreamUnexpectedError } from './stream-error.js';
|
|
15
15
|
const KIRO_API_PATTERN = /^(https?:\/\/)?q\.[a-z0-9-]+\.amazonaws\.com/;
|
|
16
16
|
const REAUTH_FAILURE_COOLDOWN_MS = 60000;
|
|
17
|
-
const MAX_STREAM_ATTEMPTS = 3;
|
|
18
17
|
function describeError(error, depth = 0) {
|
|
19
18
|
if (!(error instanceof Error))
|
|
20
19
|
return String(error);
|
|
@@ -192,7 +191,8 @@ export class RequestHandler {
|
|
|
192
191
|
account: acc.email,
|
|
193
192
|
accountId: acc.id,
|
|
194
193
|
streamAttempt,
|
|
195
|
-
maxStreamAttempts:
|
|
194
|
+
maxStreamAttempts: this.config.stream_max_attempts,
|
|
195
|
+
streamDeliveryMode: this.config.stream_buffer_until_complete ? 'buffered' : 'live',
|
|
196
196
|
...details
|
|
197
197
|
});
|
|
198
198
|
if (this.config.enable_log_effort_debug) {
|
|
@@ -296,6 +296,7 @@ export class RequestHandler {
|
|
|
296
296
|
onComplete: completeRequest,
|
|
297
297
|
onTerminal: cleanupRequest,
|
|
298
298
|
onCancel: (reason) => requestController.abort(reason),
|
|
299
|
+
bufferUntilComplete: this.config.stream_buffer_until_complete,
|
|
299
300
|
mapError: (error) => {
|
|
300
301
|
logger.error('Kiro SDK event stream iteration failed', streamLogDetails({
|
|
301
302
|
outcome: 'terminated_after_output',
|
|
@@ -323,7 +324,7 @@ export class RequestHandler {
|
|
|
323
324
|
if (e instanceof SdkEventStreamIterationError) {
|
|
324
325
|
streamFailureCount++;
|
|
325
326
|
const streamError = new UpstreamUnexpectedError(e, false);
|
|
326
|
-
if (streamFailureCount >=
|
|
327
|
+
if (streamFailureCount >= this.config.stream_max_attempts) {
|
|
327
328
|
logger.error('Kiro SDK event stream iteration failed', streamLogDetails({
|
|
328
329
|
outcome: 'exhausted',
|
|
329
330
|
platform: process.platform,
|
|
@@ -10,6 +10,7 @@ export interface SdkResponseLifecycle {
|
|
|
10
10
|
onTerminal?: () => void;
|
|
11
11
|
onCancel?: (reason: unknown) => void;
|
|
12
12
|
mapError?: (error: SdkEventStreamIterationError, emittedOutput: true) => unknown;
|
|
13
|
+
bufferUntilComplete?: boolean;
|
|
13
14
|
}
|
|
14
15
|
export declare class ResponseHandler {
|
|
15
16
|
handleSuccess(response: Response, model: string, conversationId: string, streaming: boolean): Promise<Response>;
|
|
@@ -115,6 +115,18 @@ function isSemanticChunk(chunk) {
|
|
|
115
115
|
function encodeSseChunk(chunk) {
|
|
116
116
|
return new TextEncoder().encode(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
117
117
|
}
|
|
118
|
+
function bufferedSseResponse(chunks) {
|
|
119
|
+
let index = 0;
|
|
120
|
+
return new Response(new ReadableStream({
|
|
121
|
+
pull(controller) {
|
|
122
|
+
const chunk = chunks[index++];
|
|
123
|
+
if (chunk)
|
|
124
|
+
controller.enqueue(chunk);
|
|
125
|
+
if (index >= chunks.length)
|
|
126
|
+
controller.close();
|
|
127
|
+
}
|
|
128
|
+
}, { highWaterMark: 0 }), { headers: { 'Content-Type': 'text/event-stream' } });
|
|
129
|
+
}
|
|
118
130
|
export class ResponseHandler {
|
|
119
131
|
async handleSuccess(response, model, conversationId, streaming) {
|
|
120
132
|
if (streaming) {
|
|
@@ -148,22 +160,34 @@ export class ResponseHandler {
|
|
|
148
160
|
const wrapped = wrapSdkEventStream(sdkResponse, lifecycle.signal, lifecycle.onUpstreamWaitStart, lifecycle.onUpstreamWaitEnd, lifecycle.onIterationError);
|
|
149
161
|
const transformed = transformSdkStream(wrapped.response, model, conversationId);
|
|
150
162
|
const buffered = [];
|
|
163
|
+
if (lifecycle.bufferUntilComplete) {
|
|
164
|
+
try {
|
|
165
|
+
while (true) {
|
|
166
|
+
const item = await transformed.next();
|
|
167
|
+
if (item.done) {
|
|
168
|
+
await lifecycle.onComplete?.();
|
|
169
|
+
lifecycle.onTerminal?.();
|
|
170
|
+
return bufferedSseResponse(buffered);
|
|
171
|
+
}
|
|
172
|
+
buffered.push(encodeSseChunk(item.value));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
try {
|
|
177
|
+
await transformed.return(undefined);
|
|
178
|
+
}
|
|
179
|
+
catch { }
|
|
180
|
+
await wrapped.closeRaw();
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
151
184
|
let firstSemantic;
|
|
152
185
|
while (true) {
|
|
153
186
|
const item = await transformed.next();
|
|
154
187
|
if (item.done) {
|
|
155
188
|
await lifecycle.onComplete?.();
|
|
156
189
|
lifecycle.onTerminal?.();
|
|
157
|
-
|
|
158
|
-
return new Response(new ReadableStream({
|
|
159
|
-
pull(controller) {
|
|
160
|
-
const chunk = buffered[index++];
|
|
161
|
-
if (chunk)
|
|
162
|
-
controller.enqueue(chunk);
|
|
163
|
-
if (index >= buffered.length)
|
|
164
|
-
controller.close();
|
|
165
|
-
}
|
|
166
|
-
}, { highWaterMark: 0 }), { headers: { 'Content-Type': 'text/event-stream' } });
|
|
190
|
+
return bufferedSseResponse(buffered);
|
|
167
191
|
}
|
|
168
192
|
const encoded = encodeSseChunk(item.value);
|
|
169
193
|
if (isSemanticChunk(item.value)) {
|
|
@@ -139,6 +139,8 @@ function applyEnvOverrides(config) {
|
|
|
139
139
|
sdk_response_timeout_ms: parseNumberEnv(env.KIRO_SDK_RESPONSE_TIMEOUT_MS, config.sdk_response_timeout_ms),
|
|
140
140
|
stream_event_timeout_enabled: parseBooleanEnv(env.KIRO_STREAM_EVENT_TIMEOUT_ENABLED, config.stream_event_timeout_enabled),
|
|
141
141
|
request_timeout_ms: parseNumberEnv(env.KIRO_REQUEST_TIMEOUT_MS, config.request_timeout_ms),
|
|
142
|
+
stream_buffer_until_complete: parseBooleanEnv(env.KIRO_STREAM_BUFFER_UNTIL_COMPLETE, config.stream_buffer_until_complete),
|
|
143
|
+
stream_max_attempts: parseNumberEnv(env.KIRO_STREAM_MAX_ATTEMPTS, config.stream_max_attempts),
|
|
142
144
|
token_expiry_buffer_ms: parseNumberEnv(env.KIRO_TOKEN_EXPIRY_BUFFER_MS, config.token_expiry_buffer_ms),
|
|
143
145
|
usage_sync_max_retries: parseNumberEnv(env.KIRO_USAGE_SYNC_MAX_RETRIES, config.usage_sync_max_retries),
|
|
144
146
|
auth_server_port_start: parseNumberEnv(env.KIRO_AUTH_SERVER_PORT_START, config.auth_server_port_start),
|
|
@@ -77,6 +77,17 @@ export declare const KiroConfigSchema: z.ZodObject<{
|
|
|
77
77
|
* Only used when stream_event_timeout_enabled is true.
|
|
78
78
|
*/
|
|
79
79
|
request_timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
80
|
+
/**
|
|
81
|
+
* Consume the complete Kiro event stream before exposing any semantic output
|
|
82
|
+
* downstream. This trades live token display for safe retries after a
|
|
83
|
+
* mid-stream transport failure without duplicating content or tool calls.
|
|
84
|
+
*/
|
|
85
|
+
stream_buffer_until_complete: z.ZodDefault<z.ZodBoolean>;
|
|
86
|
+
/**
|
|
87
|
+
* Maximum number of complete event-stream attempts. Buffered mode can safely
|
|
88
|
+
* use every attempt even when the failed upstream stream produced output.
|
|
89
|
+
*/
|
|
90
|
+
stream_max_attempts: z.ZodDefault<z.ZodNumber>;
|
|
80
91
|
token_expiry_buffer_ms: z.ZodDefault<z.ZodNumber>;
|
|
81
92
|
/**
|
|
82
93
|
* Opt-in leader-elected keep-alive that proactively rotates idle-account
|
|
@@ -129,6 +140,8 @@ export declare const KiroConfigSchema: z.ZodObject<{
|
|
|
129
140
|
sdk_response_timeout_ms: number;
|
|
130
141
|
stream_event_timeout_enabled: boolean;
|
|
131
142
|
request_timeout_ms: number;
|
|
143
|
+
stream_buffer_until_complete: boolean;
|
|
144
|
+
stream_max_attempts: number;
|
|
132
145
|
token_expiry_buffer_ms: number;
|
|
133
146
|
token_keepalive_enabled: boolean;
|
|
134
147
|
token_keepalive_interval_ms: number;
|
|
@@ -165,6 +178,8 @@ export declare const KiroConfigSchema: z.ZodObject<{
|
|
|
165
178
|
sdk_response_timeout_ms?: number | undefined;
|
|
166
179
|
stream_event_timeout_enabled?: boolean | undefined;
|
|
167
180
|
request_timeout_ms?: number | undefined;
|
|
181
|
+
stream_buffer_until_complete?: boolean | undefined;
|
|
182
|
+
stream_max_attempts?: number | undefined;
|
|
168
183
|
token_expiry_buffer_ms?: number | undefined;
|
|
169
184
|
token_keepalive_enabled?: boolean | undefined;
|
|
170
185
|
token_keepalive_interval_ms?: number | undefined;
|
|
@@ -109,6 +109,17 @@ export const KiroConfigSchema = z.object({
|
|
|
109
109
|
* Only used when stream_event_timeout_enabled is true.
|
|
110
110
|
*/
|
|
111
111
|
request_timeout_ms: z.number().min(30000).max(600000).default(120000),
|
|
112
|
+
/**
|
|
113
|
+
* Consume the complete Kiro event stream before exposing any semantic output
|
|
114
|
+
* downstream. This trades live token display for safe retries after a
|
|
115
|
+
* mid-stream transport failure without duplicating content or tool calls.
|
|
116
|
+
*/
|
|
117
|
+
stream_buffer_until_complete: z.boolean().default(false),
|
|
118
|
+
/**
|
|
119
|
+
* Maximum number of complete event-stream attempts. Buffered mode can safely
|
|
120
|
+
* use every attempt even when the failed upstream stream produced output.
|
|
121
|
+
*/
|
|
122
|
+
stream_max_attempts: z.number().int().min(1).max(10).default(3),
|
|
112
123
|
token_expiry_buffer_ms: z.number().min(30000).max(300000).default(300000),
|
|
113
124
|
/**
|
|
114
125
|
* Opt-in leader-elected keep-alive that proactively rotates idle-account
|
|
@@ -162,6 +173,8 @@ export const DEFAULT_CONFIG = {
|
|
|
162
173
|
sdk_response_timeout_ms: 300000,
|
|
163
174
|
stream_event_timeout_enabled: false,
|
|
164
175
|
request_timeout_ms: 120000,
|
|
176
|
+
stream_buffer_until_complete: false,
|
|
177
|
+
stream_max_attempts: 3,
|
|
165
178
|
token_expiry_buffer_ms: 300000,
|
|
166
179
|
token_keepalive_enabled: false,
|
|
167
180
|
token_keepalive_interval_ms: 600000,
|