pi-codex-search 0.1.3 → 0.1.4

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.
Files changed (2) hide show
  1. package/index.ts +96 -25
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -80,6 +80,7 @@ interface WebSearchDetails {
80
80
  partial?: boolean;
81
81
  completed?: number;
82
82
  total?: number;
83
+ elapsedMs?: number;
83
84
  }
84
85
 
85
86
  function buildToolDescription(config: ResolvedConfig): string {
@@ -292,6 +293,8 @@ function buildTool(config: ResolvedConfig) {
292
293
  );
293
294
  }
294
295
 
296
+ const startedAt = Date.now();
297
+
295
298
  const token = await ctx.modelRegistry.getApiKeyForProvider(OPENAI_CODEX_PROVIDER);
296
299
  if (!token) {
297
300
  const err = new CodexError(
@@ -477,7 +480,9 @@ function buildTool(config: ResolvedConfig) {
477
480
 
478
481
  return {
479
482
  content: [{ type: "text", text: formatToolText(successes, failures) }],
480
- details: buildDetails(config, model, freshness, searchContextSize, successes, failures),
483
+ details: buildDetails(config, model, freshness, searchContextSize, successes, failures, {
484
+ elapsedMs: Date.now() - startedAt,
485
+ }),
481
486
  };
482
487
  }
483
488
 
@@ -584,7 +589,9 @@ function buildTool(config: ResolvedConfig) {
584
589
 
585
590
  return {
586
591
  content: [{ type: "text", text: formatToolText(successes, failures) }],
587
- details: buildDetails(config, model, freshness, searchContextSize, successes, failures),
592
+ details: buildDetails(config, model, freshness, searchContextSize, successes, failures, {
593
+ elapsedMs: Date.now() - startedAt,
594
+ }),
588
595
  };
589
596
  },
590
597
 
@@ -604,7 +611,7 @@ function buildTool(config: ResolvedConfig) {
604
611
  } else {
605
612
  text += ` ${theme.fg("accent", `${labels.length} actions`)}`;
606
613
  }
607
- text += theme.fg("dim", ` [${config.searchApi}/${ctxSize}/${fresh}]`);
614
+ text += theme.fg("dim", ` ${formatModeLabel(config.searchApi, ctxSize, fresh)}`);
608
615
  if (labels.length > 1) {
609
616
  text += `\n${renderCallQueries(labels, theme)}`;
610
617
  }
@@ -627,26 +634,26 @@ function buildTool(config: ResolvedConfig) {
627
634
  const total = details.queryCount;
628
635
  const failed = details.failedQueryCount;
629
636
  const ok = total - failed;
630
- const sourceCount = details.successes.reduce((acc, s) => acc + countSuccessSources(s), 0);
637
+ const resultSuffix = formatResultSuffix(details);
631
638
 
632
639
  let header: string;
633
640
  if (ok === 0) {
634
641
  header = theme.fg("warning", `⚠ Web search failed (${details.failure?.kind ?? "unknown"})`);
635
642
  } else if (failed > 0) {
636
- const sourceSuffix =
637
- sourceCount > 0 ? ` · ${sourceCount} source${sourceCount === 1 ? "" : "s"}` : "";
638
- header = theme.fg("warning", `⚠ ${ok}/${total} queries succeeded${sourceSuffix}`);
643
+ header = theme.fg(
644
+ "warning",
645
+ `Did ${ok}/${total} ${formatOperationNoun(details, total)}${formatDurationSuffix(details.elapsedMs)}${resultSuffix}`,
646
+ );
639
647
  } else {
640
- const querySuffix = total === 1 ? "" : ` across ${total} queries`;
641
- const sourceText =
642
- sourceCount > 0
643
- ? `${sourceCount} source${sourceCount === 1 ? "" : "s"}`
644
- : "Search completed";
645
- header = theme.fg("success", `✓ ${sourceText}${querySuffix}`);
648
+ const operationCount = countSuccessOperations(details);
649
+ header = theme.fg(
650
+ "success",
651
+ `Did ${operationCount} ${formatOperationNoun(details, operationCount)}${formatDurationSuffix(details.elapsedMs)}${resultSuffix}`,
652
+ );
646
653
  }
647
654
  header += theme.fg(
648
655
  "muted",
649
- ` [${details.api}/${details.searchContextSize}/${details.freshness}]`,
656
+ ` ${formatModeLabel(details.api, details.searchContextSize, details.freshness)}`,
650
657
  );
651
658
 
652
659
  if (!expanded) {
@@ -736,7 +743,7 @@ function buildDetails(
736
743
  searchContextSize: SearchContextSize,
737
744
  successes: QuerySuccess[],
738
745
  failures: QueryFailure[],
739
- extra?: { partial?: boolean; completed?: number; total?: number },
746
+ extra?: { partial?: boolean; completed?: number; total?: number; elapsedMs?: number },
740
747
  ): WebSearchDetails {
741
748
  const queries = successes.map((s) => s.query).concat(failures.map((f) => f.query));
742
749
  return {
@@ -788,8 +795,54 @@ function formatSuccessBlock(success: QuerySuccess, multiple: boolean): string {
788
795
  return multiple ? `## Query: ${success.query}\n\n${body}` : body;
789
796
  }
790
797
 
791
- function countSuccessSources(success: QuerySuccess): number {
792
- return success.citations.length + Object.keys(success.refIds ?? {}).length;
798
+ function countSuccessCitations(details: WebSearchDetails): number {
799
+ return details.successes.reduce((acc, success) => acc + success.citations.length, 0);
800
+ }
801
+
802
+ function countSuccessWebActions(details: WebSearchDetails): number {
803
+ return details.successes.reduce((acc, success) => acc + success.searchCalls.length, 0);
804
+ }
805
+
806
+ function countSuccessOperations(details: WebSearchDetails): number {
807
+ if (details.api === "responses") return details.queryCount;
808
+ const count = countSuccessWebActions(details);
809
+ return count > 0 ? count : details.queryCount;
810
+ }
811
+
812
+ function formatOperationNoun(details: WebSearchDetails, count: number): string {
813
+ const singular = details.api === "standalone" ? "action" : "search";
814
+ const plural = details.api === "standalone" ? "actions" : "searches";
815
+ return count === 1 ? singular : plural;
816
+ }
817
+
818
+ function formatResultSuffix(details: WebSearchDetails): string {
819
+ const parts: string[] = [];
820
+ const webActionCount = countSuccessWebActions(details);
821
+ if (details.api === "responses" && webActionCount > 0) {
822
+ parts.push(`${webActionCount} web action${webActionCount === 1 ? "" : "s"}`);
823
+ }
824
+
825
+ const citationCount = countSuccessCitations(details);
826
+ if (citationCount > 0) {
827
+ parts.push(`${citationCount} source${citationCount === 1 ? "" : "s"}`);
828
+ }
829
+
830
+ return parts.length > 0 ? ` · ${parts.join(" · ")}` : "";
831
+ }
832
+
833
+ function formatDurationSuffix(elapsedMs: number | undefined): string {
834
+ if (elapsedMs === undefined) return "";
835
+ if (elapsedMs < 1000) return ` in ${elapsedMs}ms`;
836
+ return ` in ${Math.max(1, Math.round(elapsedMs / 1000))}s`;
837
+ }
838
+
839
+ function formatModeLabel(
840
+ api: string,
841
+ searchContextSize: string,
842
+ freshness: string | undefined,
843
+ ): string {
844
+ if (api === "standalone") return `[${api}/${searchContextSize}]`;
845
+ return `[${api}/${searchContextSize}/${freshness ?? "live"}]`;
793
846
  }
794
847
 
795
848
  function formatFailureBlock(failure: QueryFailure, multiple: boolean): string {
@@ -808,8 +861,8 @@ function renderPartial(details: WebSearchDetails | undefined, theme: Theme): str
808
861
 
809
862
  function renderCollapsedPreview(details: WebSearchDetails, theme: Theme): string {
810
863
  const lines: string[] = [];
811
- const queriesPreview = renderQueriesPreview(details.queries, theme);
812
- if (queriesPreview) lines.push(queriesPreview);
864
+ const successPreview = renderSuccessPreview(details, theme);
865
+ if (successPreview) lines.push(successPreview);
813
866
 
814
867
  const firstFailure = details.failures[0];
815
868
  if (firstFailure) {
@@ -818,12 +871,30 @@ function renderCollapsedPreview(details: WebSearchDetails, theme: Theme): string
818
871
  return lines.join("\n");
819
872
  }
820
873
 
821
- function renderQueriesPreview(queries: string[], theme: Theme): string {
822
- if (queries.length === 0) return "";
823
- if (queries.length === 1) {
824
- return theme.fg("accent", `Query: ${formatInline(queries[0], 120)}`);
825
- }
826
- return [theme.fg("accent", "Queries:"), renderCallQueries(queries, theme)].join("\n");
874
+ function renderSuccessPreview(details: WebSearchDetails, theme: Theme): string {
875
+ if (details.api !== "standalone") return "";
876
+ const success = details.successes[0];
877
+ if (!success) return "";
878
+ const line = firstNonEmptyLine(success.text);
879
+ if (!line) return "";
880
+ const actionType = success.searchCalls[0]?.actionType;
881
+ return theme.fg("dim", `${formatStandalonePreviewLabel(actionType)}: ${formatInline(line, 120)}`);
882
+ }
883
+
884
+ function firstNonEmptyLine(text: string): string {
885
+ return (
886
+ text
887
+ .split("\n")
888
+ .map((line) => line.trim())
889
+ .find((line) => line.length > 0) ?? ""
890
+ );
891
+ }
892
+
893
+ function formatStandalonePreviewLabel(actionType: string | undefined): string {
894
+ if (actionType === "open_page" || actionType === "click") return "Opened";
895
+ if (actionType === "find_in_page") return "Found";
896
+ if (actionType === "screenshot") return "Screenshot";
897
+ return "Result";
827
898
  }
828
899
 
829
900
  function renderCallQueries(queries: unknown[], theme: Theme): string {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-codex-search",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Pi extension for Codex Web Search — reuse your ChatGPT Plus/Pro Codex subscription in pi-coding-agent",
5
5
  "keywords": [
6
6
  "chatgpt",