@f5-sales-demo/pi-ai 21.12.2 → 21.13.1

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/CHANGELOG.md CHANGED
@@ -10,6 +10,12 @@
10
10
 
11
11
  ### Fixed
12
12
 
13
+ - Prevented case-insensitive Windows environment lookup from replacing literal stored credentials,
14
+ and delayed Cursor success until the HTTP/2/CONNECT protocol terminates cleanly so late stream
15
+ errors remain visible. Semantically ported from
16
+ [can1357/oh-my-pi#7362](https://github.com/can1357/oh-my-pi/pull/7362) and
17
+ [#5636](https://github.com/can1357/oh-my-pi/pull/5636)
18
+ ([#3711](https://github.com/f5-sales-demo/xcsh/issues/3711)).
13
19
  - Exported the canonical provider credential-shape matcher so xcsh can protect otherwise
14
20
  unconfigured tokens reversibly before provider submission, preserving exact-match local edits.
15
21
  Semantically ported from
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5-sales-demo/pi-ai",
4
- "version": "21.12.2",
4
+ "version": "21.13.1",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://github.com/f5-sales-demo/xcsh",
7
7
  "author": "Can Boluk",
@@ -46,7 +46,7 @@
46
46
  "@anthropic-ai/sdk": "^0.115",
47
47
  "@aws-sdk/client-bedrock-runtime": "^3",
48
48
  "@bufbuild/protobuf": "^2.11",
49
- "@f5-sales-demo/pi-utils": "21.12.2",
49
+ "@f5-sales-demo/pi-utils": "21.13.1",
50
50
  "@google/genai": "^2.15",
51
51
  "@sinclair/typebox": "^0.34",
52
52
  "@smithy/node-http-handler": "^4.4",
@@ -10,7 +10,7 @@
10
10
  import { Database, type Statement } from "bun:sqlite";
11
11
  import * as fs from "node:fs/promises";
12
12
  import * as path from "node:path";
13
- import { getAgentDbPath, logger } from "@f5-sales-demo/pi-utils";
13
+ import { $envExact, getAgentDbPath, logger } from "@f5-sales-demo/pi-utils";
14
14
  import { getEnvApiKey } from "./stream";
15
15
  import type { Provider } from "./types";
16
16
  import type {
@@ -149,7 +149,7 @@ export type AuthStorageOptions = {
149
149
  * Does NOT support "!command" syntax (that requires pi-natives).
150
150
  */
151
151
  async function defaultConfigValueResolver(config: string): Promise<string | undefined> {
152
- const envValue = process.env[config];
152
+ const envValue = $envExact(config);
153
153
  return envValue || config;
154
154
  }
155
155
 
@@ -325,6 +325,27 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
325
325
  let h2Client: http2.ClientHttp2Session | null = null;
326
326
  let h2Request: http2.ClientHttp2Stream | null = null;
327
327
  let heartbeatTimer: NodeJS.Timeout | null = null;
328
+ const h2Completion = Promise.withResolvers<void>();
329
+ let h2Settled = false;
330
+ let sawTurnEnded = false;
331
+ let endStreamError: Error | null = null;
332
+ const settleH2 = (error?: unknown): void => {
333
+ if (h2Settled) return;
334
+ h2Settled = true;
335
+ if (error !== undefined) {
336
+ h2Completion.reject(error);
337
+ return;
338
+ }
339
+ if (endStreamError) {
340
+ h2Completion.reject(endStreamError);
341
+ return;
342
+ }
343
+ if (!sawTurnEnded) {
344
+ h2Completion.reject(new Error("Cursor stream ended before turnEnded"));
345
+ return;
346
+ }
347
+ h2Completion.resolve();
348
+ };
328
349
 
329
350
  try {
330
351
  const apiKey = options?.apiKey;
@@ -346,6 +367,7 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
346
367
 
347
368
  const baseUrl = model.baseUrl || CURSOR_API_URL;
348
369
  h2Client = http2.connect(baseUrl);
370
+ h2Client.on("error", error => settleH2(error));
349
371
 
350
372
  h2Request = h2Client.request({
351
373
  ":method": "POST",
@@ -363,7 +385,6 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
363
385
  stream.push({ type: "start", partial: output });
364
386
 
365
387
  let pendingBuffer = Buffer.alloc(0);
366
- let endStreamError: Error | null = null;
367
388
  let currentTextBlock: (TextContent & { index: number }) | null = null;
368
389
  let currentThinkingBlock: (ThinkingContent & { index: number }) | null = null;
369
390
  let currentToolCall: ToolCallState | null = null;
@@ -400,8 +421,6 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
400
421
  conversationStateCache.set(conversationId, checkpoint);
401
422
  };
402
423
 
403
- let resolveH2: (() => void) | undefined;
404
-
405
424
  h2Request.on("data", (chunk: Buffer) => {
406
425
  pendingBuffer = Buffer.concat([pendingBuffer, chunk]);
407
426
 
@@ -417,6 +436,7 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
417
436
  const endError = parseConnectEndStream(messageBytes);
418
437
  if (endError) {
419
438
  endStreamError = endError;
439
+ settleH2(endError);
420
440
  h2Request?.close();
421
441
  }
422
442
  continue;
@@ -443,13 +463,8 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
443
463
  log("error", "handleServerMessage", { error: String(error) });
444
464
  });
445
465
 
446
- // Resolve only on explicit turnEnded. stopReason defaults to "stop"
447
- // and is not a reliable signal for stream completion.
448
- if (isTurnEnded && resolveH2) {
449
- const r = resolveH2;
450
- resolveH2 = undefined;
451
- r();
452
- }
466
+ // Application completion is not protocol success; wait for a clean HTTP/2 end.
467
+ if (isTurnEnded) sawTurnEnded = true;
453
468
  } catch (e) {
454
469
  log("error", "parseServerMessage", { error: String(e) });
455
470
  }
@@ -471,32 +486,24 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
471
486
 
472
487
  heartbeatTimer = setInterval(sendHeartbeat, 5000);
473
488
 
474
- const h2Completion = Promise.withResolvers<void>();
475
- resolveH2 = h2Completion.resolve;
476
-
477
489
  h2Request!.on("trailers", trailers => {
478
490
  const status = trailers["grpc-status"];
479
491
  const msg = trailers["grpc-message"];
480
- if (status && status !== "0") {
481
- h2Completion.reject(new Error(`gRPC error ${status}: ${decodeURIComponent(String(msg || ""))}`));
492
+ if (status && status !== "0" && !endStreamError) {
493
+ endStreamError = new Error(`gRPC error ${status}: ${decodeURIComponent(String(msg || ""))}`);
482
494
  }
483
495
  });
484
496
 
485
497
  h2Request!.on("end", () => {
486
- resolveH2 = undefined;
487
- if (endStreamError) {
488
- h2Completion.reject(endStreamError);
489
- return;
490
- }
491
- h2Completion.resolve();
498
+ settleH2();
492
499
  });
493
500
 
494
- h2Request!.on("error", h2Completion.reject);
501
+ h2Request!.on("error", error => settleH2(error));
495
502
 
496
503
  if (options?.signal) {
497
504
  options.signal.addEventListener("abort", () => {
498
505
  h2Request?.close();
499
- h2Completion.reject(new Error("Request was aborted"));
506
+ settleH2(new Error("Request was aborted"));
500
507
  });
501
508
  }
502
509
  await h2Completion.promise;