@estebanforge/pi-antigravity-bridge 1.6.1 → 1.6.2

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
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.6.2] - 2026-09-19
6
+
7
+ ### Fixed
8
+
9
+ - **Compatibility with host 0.86.0's normalized provider stream input.** Stream entry points now receive a branded `TranscriptContext` (`{ messages }` only): the `context.systemPrompt` / `context.tools` fields are gone, and only `normalizeContext()` produces the new type. G10 fresh-conversation delivery read `context.systemPrompt`, so every fresh agy conversation silently lost the system prompt block (operating instructions, AGENTS.md project context, tool-priority note); the prompt now comes from the transcript via `getCurrentSystemPrompt(context.messages)`. `emitToolUse` arguments are restricted to `Record<string, JsonValue>` per the tightened `ToolCall.arguments` contract (driver args arrive JSON-decoded off the wire, so the two call-site casts are sound). Test fixtures in `provider-{sysprompt,streaming,late-result,escalation}.test.ts` and the `scripts/test-provider.ts` smoke context wrap raw objects with `normalizeContext()`. Dev pins `@earendil-works/*` raised `^0.85.0` to `^0.86.0`; `tsc` clean, 360 tests green.
10
+
5
11
  ## [1.6.1] - 2026-09-18
6
12
 
7
13
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@estebanforge/pi-antigravity-bridge",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
4
4
  "description": "Gemini provider for Pi on the Antigravity ACP server (official Google ACP) or the stream-json agy CLI. antigravity/* models in Pi's /model picker, no-patch MCP bridge: agy runs Pi's tools. ToS safe to use.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -59,10 +59,10 @@
59
59
  }
60
60
  },
61
61
  "devDependencies": {
62
- "@earendil-works/pi-ai": "^0.85.0",
63
- "@earendil-works/pi-coding-agent": "^0.85.0",
64
- "@earendil-works/pi-server": "^0.85.0",
65
- "@earendil-works/pi-tui": "^0.85.0",
62
+ "@earendil-works/pi-ai": "^0.86.0",
63
+ "@earendil-works/pi-coding-agent": "^0.86.0",
64
+ "@earendil-works/pi-server": "^0.86.0",
65
+ "@earendil-works/pi-tui": "^0.86.0",
66
66
  "@types/node": "^22.0.0",
67
67
  "tsx": "^4.19.0",
68
68
  "typebox": "^1.1.38",
package/src/provider.ts CHANGED
@@ -19,13 +19,15 @@
19
19
 
20
20
  import {
21
21
  createAssistantMessageEventStream,
22
+ getCurrentSystemPrompt,
22
23
  type AssistantMessage,
23
24
  type AssistantMessageEventStream,
24
- type Context,
25
+ type JsonValue,
25
26
  type Message,
26
27
  type Model,
27
28
  type SimpleStreamOptions,
28
29
  type ThinkingLevel,
30
+ type TranscriptContext,
29
31
  type Usage,
30
32
  } from "@earendil-works/pi-ai";
31
33
  import fs from "node:fs";
@@ -59,7 +61,7 @@ function zeroUsage(): Usage {
59
61
  /** Extract the latest user message as a flat prompt string. agy maintains its
60
62
  * own conversation history via --conversation, so we collapse pi's structured
61
63
  * message to text. Returns null if the last message isn't a user message. */
62
- function extractUserPrompt(context: Context): string | null {
64
+ function extractUserPrompt(context: TranscriptContext): string | null {
63
65
  const last = context.messages[context.messages.length - 1];
64
66
  if (!last || last.role !== "user") return null;
65
67
  const content = last.content;
@@ -75,7 +77,7 @@ function extractUserPrompt(context: Context): string | null {
75
77
  /** Image blocks of the latest user message (pi-ai ImageContent: base64 data
76
78
  * + mimeType). The ACP engine forwards them as typed content blocks; the
77
79
  * stream-json CLI prompt is text-only, so its driver simply ignores these. */
78
- function extractImages(context: Context): Array<{ data: string; mimeType: string }> {
80
+ function extractImages(context: TranscriptContext): Array<{ data: string; mimeType: string }> {
79
81
  const last = context.messages[context.messages.length - 1];
80
82
  if (!last || last.role !== "user" || typeof last.content === "string") return [];
81
83
  return last.content
@@ -100,7 +102,8 @@ const DIGEST_PREAMBLE =
100
102
 
101
103
  // --- pi system prompt (G10) ----------------------------------------------------
102
104
  //
103
- // pi composes context.systemPrompt every turn: its own operating instructions
105
+ // pi normalizes the system prompt into the transcript every turn (read via
106
+ // getCurrentSystemPrompt): its own operating instructions
104
107
  // plus every AGENTS.md/CLAUDE.md it loaded (global agent dir first, then
105
108
  // ancestors). The provider used to drop it, so agy models never saw the user's
106
109
  // machine-level or project-level instructions. agy has no system-prompt flag
@@ -921,13 +924,14 @@ function nextRtId(kind: "nat" | "wrap"): string {
921
924
  return `${kind}-${++RT_SEQ}`;
922
925
  }
923
926
 
924
- /** Emit a complete toolCall block and end the pi call with toolUse. */
927
+ /** Emit a complete toolCall block and end the pi call with toolUse.
928
+ * pi 0.86 restricts ToolCall.arguments to JSON-compatible values. */
925
929
  function emitToolUse(
926
930
  stream: AssistantMessageEventStream,
927
931
  blocks: BlockState,
928
932
  id: string,
929
933
  name: string,
930
- args: Record<string, unknown>,
934
+ args: Record<string, JsonValue>,
931
935
  ): void {
932
936
  const partial = blocks.partial;
933
937
  closeThinking(stream, blocks);
@@ -1050,7 +1054,7 @@ function acpEditFileArg(args: Record<string, unknown>): string | undefined {
1050
1054
  emitToolUse(stream, blocks, id, mapped.tool, {
1051
1055
  reasoning: `re-exec of agy ${activity.name} for display`,
1052
1056
  ...mapped.args,
1053
- });
1057
+ } as Record<string, JsonValue>);
1054
1058
  return "parked";
1055
1059
  }
1056
1060
  {
@@ -1067,7 +1071,9 @@ function acpEditFileArg(args: Record<string, unknown>): string | undefined {
1067
1071
  case "bridge_call": {
1068
1072
  // Park the pi call: real tool name + args, toolUse stopReason. pi
1069
1073
  // executes; the toolResult returns on the next stream call.
1070
- emitToolUse(stream, blocks, activity.callId, activity.name, activity.args);
1074
+ // Driver args are JSON-decoded off the agy wire, so the JsonValue
1075
+ // cast is sound.
1076
+ emitToolUse(stream, blocks, activity.callId, activity.name, activity.args as Record<string, JsonValue>);
1071
1077
  return "parked";
1072
1078
  }
1073
1079
  }
@@ -1077,7 +1083,7 @@ function acpEditFileArg(args: Record<string, unknown>): string | undefined {
1077
1083
  async function runTurnDriver(
1078
1084
  stream: AssistantMessageEventStream,
1079
1085
  model: Model<Api>,
1080
- context: Context,
1086
+ context: TranscriptContext,
1081
1087
  options: SimpleStreamOptions | undefined,
1082
1088
  entries: AgyModelEntry[],
1083
1089
  store: SessionStore,
@@ -1179,7 +1185,9 @@ async function runTurnDriver(
1179
1185
  // Fresh conversation only: agy stores the block in its own history, so
1180
1186
  // re-sending it every turn would bloat each prompt and bust the cache.
1181
1187
  const sysPrompt =
1182
- config.systemPrompt && !existing?.conversationId ? context.systemPrompt : undefined;
1188
+ config.systemPrompt && !existing?.conversationId
1189
+ ? getCurrentSystemPrompt(context.messages) || undefined
1190
+ : undefined;
1183
1191
  const fullPrompt =
1184
1192
  late.length > 0
1185
1193
  ? buildLateResultPrompt(late, prompt || undefined)
@@ -1258,7 +1266,7 @@ async function runTurnDriver(
1258
1266
  * fallback). */
1259
1267
  export function createStreamSimple(
1260
1268
  deps: StreamSimpleDeps,
1261
- ): (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream {
1269
+ ): (model: Model<Api>, context: TranscriptContext, options?: SimpleStreamOptions) => AssistantMessageEventStream {
1262
1270
  const { entries, store, roundTrips } = deps;
1263
1271
 
1264
1272
  return function streamSimple(model, context, options) {