@benjamolina/pi-antigravity-guard 0.4.5 → 0.4.7

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 CHANGED
@@ -22,9 +22,9 @@ All seven rows below are registered for text. The exact `antigravity-gemini-3.8-
22
22
  | `antigravity-gemini-3.7-flash` | off, low, medium, high | Enabled (off, low, medium, high) | pi-json-tool-loop/gemini-3.7-flash-off-v1 |
23
23
  | `antigravity-gemini-3.6-flash` | off, low, medium, high | Enabled (off, low, medium, high) | pi-json-tool-loop/gemini-3.6-flash-off-v1 |
24
24
  | `antigravity-gemini-3.1-pro` | off, low, high | Enabled (off, low, high) | pi-json-tool-loop/gemini-3.1-pro-off-v1 |
25
- | `antigravity-claude-sonnet-4.6` | off, high | Disabled | claude-continuity-unproven |
26
- | `antigravity-claude-opus-4.6-thinking` | off, high | Disabled | claude-continuity-unproven |
27
- | `antigravity-gpt-oss-120b` | off, medium | Disabled | missing-direct-evidence |
25
+ | `antigravity-claude-sonnet-4.6` | off, high | Enabled (off, high) | pi-json-tool-loop/claude-sonnet-4.6-off-v1 |
26
+ | `antigravity-claude-opus-4.6-thinking` | off, high | Enabled (off, high) | pi-json-tool-loop/claude-opus-4.6-thinking-off-v1 |
27
+ | `antigravity-gpt-oss-120b` | off, medium | Enabled (off, medium) | pi-json-tool-loop/gpt-oss-120b-off-v1 |
28
28
 
29
29
  `off` is available for every row even though it is omitted from Pi's level map; omitted reasoning also selects that row's `off` route. Unsupported levels are not advertised and are rejected before transport. `antigravity-gemini-3.5-flash` is not registered or advertised as supported because its recorded HTTP-200 response lacks the strict terminal metadata required for admission. A future `fixture-qualified` route remains disabled for ordinary tool use; only separately authorized direct validation for the exact route may make it enabled.
30
30
 
package/dist/catalog.d.ts CHANGED
@@ -8,7 +8,8 @@ export interface ToolEvidenceRef {
8
8
  readonly wireModel: string;
9
9
  }
10
10
  export declare const GEMINI_PARAMETERS_JSON_SCHEMA_PROFILE: "gemini-parameters-json-schema";
11
- export type ToolSchemaProfile = typeof GEMINI_PARAMETERS_JSON_SCHEMA_PROFILE;
11
+ export declare const CLAUDE_CUSTOM_PARAMETERS_PROFILE: "claude-custom-parameters";
12
+ export type ToolSchemaProfile = typeof GEMINI_PARAMETERS_JSON_SCHEMA_PROFILE | typeof CLAUDE_CUSTOM_PARAMETERS_PROFILE;
12
13
  export type ToolCapability = {
13
14
  readonly state: "disabled";
14
15
  readonly contractRevision: 1;
@@ -316,7 +317,7 @@ declare const CATALOG: readonly [{
316
317
  };
317
318
  };
318
319
  readonly replay: {
319
- readonly kind: "strip";
320
+ readonly kind: "same-public-model";
320
321
  };
321
322
  readonly response: {
322
323
  readonly kind: "gemini-envelope";
@@ -356,7 +357,7 @@ declare const CATALOG: readonly [{
356
357
  };
357
358
  };
358
359
  readonly replay: {
359
- readonly kind: "strip";
360
+ readonly kind: "same-public-model";
360
361
  };
361
362
  readonly response: {
362
363
  readonly kind: "gemini-envelope";
@@ -394,7 +395,7 @@ declare const CATALOG: readonly [{
394
395
  };
395
396
  };
396
397
  readonly replay: {
397
- readonly kind: "strip";
398
+ readonly kind: "same-public-model";
398
399
  };
399
400
  readonly response: {
400
401
  readonly kind: "gemini-envelope";
@@ -436,7 +437,7 @@ export declare function toPiModelDescriptor(entry: CatalogEntry): {
436
437
  medium: string | null;
437
438
  minimal: string | null;
438
439
  };
439
- input: "text"[];
440
+ input: ("text" | "image")[];
440
441
  cost: {
441
442
  input: number;
442
443
  output: number;
package/dist/catalog.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const ANSWER_RESERVE = 1024;
2
2
  export const GEMINI_PARAMETERS_JSON_SCHEMA_PROFILE = "gemini-parameters-json-schema";
3
+ export const CLAUDE_CUSTOM_PARAMETERS_PROFILE = "claude-custom-parameters";
3
4
  const CATALOG = freeze(defineCatalog([{
4
5
  publicId: "antigravity-gemini-3.8-flash",
5
6
  descriptor: {
@@ -72,10 +73,10 @@ const CATALOG = freeze(defineCatalog([{
72
73
  maxTokens: 64_000,
73
74
  },
74
75
  routes: {
75
- off: { wireModel: "claude-sonnet-4-6", thinking: { kind: "budget", budget: 0, includeThoughts: false }, tools: disabled("claude-continuity-unproven") },
76
- high: { wireModel: "claude-sonnet-4-6", thinking: { kind: "budget", budget: 1024, includeThoughts: true }, tools: disabled("claude-continuity-unproven") },
76
+ off: { wireModel: "claude-sonnet-4-6", thinking: { kind: "budget", budget: 0, includeThoughts: false }, tools: enabled({ record: "pi-json-tool-loop", revision: "claude-sonnet-4.6-off-v1", publicModelId: "antigravity-claude-sonnet-4.6", reasoning: "off", wireModel: "claude-sonnet-4-6" }, CLAUDE_CUSTOM_PARAMETERS_PROFILE) },
77
+ high: { wireModel: "claude-sonnet-4-6", thinking: { kind: "budget", budget: 1024, includeThoughts: true }, tools: enabled({ record: "pi-json-tool-loop", revision: "claude-sonnet-4.6-high-v1", publicModelId: "antigravity-claude-sonnet-4.6", reasoning: "high", wireModel: "claude-sonnet-4-6" }, CLAUDE_CUSTOM_PARAMETERS_PROFILE) },
77
78
  },
78
- replay: { kind: "strip" },
79
+ replay: { kind: "same-public-model" },
79
80
  response: { kind: "gemini-envelope", family: "claude" },
80
81
  }, {
81
82
  publicId: "antigravity-claude-opus-4.6-thinking",
@@ -86,10 +87,10 @@ const CATALOG = freeze(defineCatalog([{
86
87
  maxTokens: 64_000,
87
88
  },
88
89
  routes: {
89
- off: { wireModel: "claude-opus-4-6-thinking", thinking: { kind: "budget", budget: 0, includeThoughts: false }, tools: disabled("claude-continuity-unproven") },
90
- high: { wireModel: "claude-opus-4-6-thinking", thinking: { kind: "budget", budget: 1024, includeThoughts: true }, tools: disabled("claude-continuity-unproven") },
90
+ off: { wireModel: "claude-opus-4-6-thinking", thinking: { kind: "budget", budget: 0, includeThoughts: false }, tools: enabled({ record: "pi-json-tool-loop", revision: "claude-opus-4.6-thinking-off-v1", publicModelId: "antigravity-claude-opus-4.6-thinking", reasoning: "off", wireModel: "claude-opus-4-6-thinking" }, CLAUDE_CUSTOM_PARAMETERS_PROFILE) },
91
+ high: { wireModel: "claude-opus-4-6-thinking", thinking: { kind: "budget", budget: 1024, includeThoughts: true }, tools: enabled({ record: "pi-json-tool-loop", revision: "claude-opus-4.6-thinking-high-v1", publicModelId: "antigravity-claude-opus-4.6-thinking", reasoning: "high", wireModel: "claude-opus-4-6-thinking" }, CLAUDE_CUSTOM_PARAMETERS_PROFILE) },
91
92
  },
92
- replay: { kind: "strip" },
93
+ replay: { kind: "same-public-model" },
93
94
  response: { kind: "gemini-envelope", family: "claude" },
94
95
  }, {
95
96
  publicId: "antigravity-gpt-oss-120b",
@@ -100,10 +101,10 @@ const CATALOG = freeze(defineCatalog([{
100
101
  maxTokens: 32_768,
101
102
  },
102
103
  routes: {
103
- off: { wireModel: "gpt-oss-120b-medium", thinking: { kind: "omit" }, tools: disabled("missing-direct-evidence") },
104
- medium: { wireModel: "gpt-oss-120b-medium", thinking: { kind: "budget", budget: 8192, includeThoughts: true }, tools: disabled("missing-direct-evidence") },
104
+ off: { wireModel: "gpt-oss-120b-medium", thinking: { kind: "omit" }, tools: enabled({ record: "pi-json-tool-loop", revision: "gpt-oss-120b-off-v1", publicModelId: "antigravity-gpt-oss-120b", reasoning: "off", wireModel: "gpt-oss-120b-medium" }, CLAUDE_CUSTOM_PARAMETERS_PROFILE) },
105
+ medium: { wireModel: "gpt-oss-120b-medium", thinking: { kind: "budget", budget: 8192, includeThoughts: true }, tools: enabled({ record: "pi-json-tool-loop", revision: "gpt-oss-120b-medium-v1", publicModelId: "antigravity-gpt-oss-120b", reasoning: "medium", wireModel: "gpt-oss-120b-medium" }, CLAUDE_CUSTOM_PARAMETERS_PROFILE) },
105
106
  },
106
- replay: { kind: "strip" },
107
+ replay: { kind: "same-public-model" },
107
108
  response: { kind: "gemini-envelope", family: "gpt-oss" },
108
109
  }]));
109
110
  const lookup = new Map(CATALOG.map((entry) => [entry.publicId, entry]));
@@ -179,12 +180,13 @@ export function resolveToolCapability(capability, publicModelId, reasoning, wire
179
180
  return capability;
180
181
  }
181
182
  export function toPiModelDescriptor(entry) {
183
+ const isMultimodal = entry.response.family === "gemini" || entry.response.family === "claude";
182
184
  return {
183
185
  id: entry.publicId,
184
- name: entry.descriptor.name,
186
+ name: entry.descriptor.name.replace(", text only", ""),
185
187
  reasoning: true,
186
188
  thinkingLevelMap: { ...entry.descriptor.thinkingLevelMap },
187
- input: ["text"],
189
+ input: (isMultimodal ? ["text", "image"] : ["text"]),
188
190
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
189
191
  contextWindow: entry.descriptor.contextWindow,
190
192
  maxTokens: entry.descriptor.maxTokens,
package/dist/context.d.ts CHANGED
@@ -2,9 +2,13 @@ import type { Context, Model, SimpleStreamOptions } from "@earendil-works/pi-ai"
2
2
  import { type GenerationSelection } from "./catalog.ts";
3
3
  import { type ToolReplayDiagnostics } from "./tool-context.ts";
4
4
  interface Part {
5
- text: string;
5
+ text?: string;
6
6
  thought?: true;
7
7
  thoughtSignature?: string;
8
+ inlineData?: {
9
+ mimeType: string;
10
+ data: string;
11
+ };
8
12
  }
9
13
  interface Content {
10
14
  role: "user" | "model";
@@ -13,7 +17,8 @@ interface Content {
13
17
  interface GeminiFunctionDeclaration {
14
18
  readonly name: string;
15
19
  readonly description: string;
16
- readonly parametersJsonSchema: object;
20
+ readonly parametersJsonSchema?: object;
21
+ readonly parameters?: object;
17
22
  }
18
23
  export interface GenerationRequest {
19
24
  project: string;
package/dist/context.js CHANGED
@@ -1,4 +1,5 @@
1
- import { getCatalogEntry, resolveGenerationSelection } from "./catalog.js";
1
+ import { CLAUDE_CUSTOM_PARAMETERS_PROFILE, getCatalogEntry, resolveGenerationSelection } from "./catalog.js";
2
+ import { normalizeCustomToolSchema } from "./tool-schema.js";
2
3
  import { hasToolContext, prepareToolContext, replayToolHistory } from "./tool-context.js";
3
4
  const MAX_TEXT_BYTES = 8 * 1024 * 1024;
4
5
  const PROVIDER = "antigravity-guard";
@@ -36,7 +37,13 @@ export function serializeContext(input, injectedSelection, onDiagnostics) {
36
37
  contents,
37
38
  ...(systemInstruction ? { systemInstruction } : {}),
38
39
  ...(prepared ? {
39
- tools: [{ functionDeclarations: prepared.declarations.map(({ name, description, parameters }) => ({ name, description, parametersJsonSchema: parameters })) }],
40
+ tools: [{ functionDeclarations: prepared.declarations.map(({ name, description, parameters }) => ({
41
+ name,
42
+ description,
43
+ ...(selection.tools.state === "enabled" && selection.tools.schemaProfile === CLAUDE_CUSTOM_PARAMETERS_PROFILE
44
+ ? { parameters: normalizeCustomToolSchema(parameters) }
45
+ : { parametersJsonSchema: parameters }),
46
+ })) }],
40
47
  ...(prepared.mode === "NONE" ? { toolConfig: { functionCallingConfig: { mode: "NONE" } } } : {}),
41
48
  } : {}),
42
49
  generationConfig,
@@ -77,7 +84,7 @@ export function serializeTextContext(input) {
77
84
  fail("Unsupported context role for this text-only provider.");
78
85
  const assistant = role === "assistant";
79
86
  const parts = messageParts(field(message, "content", true), assistant, assistant && entry.replay.kind === "same-public-model" && isSameProviderAndModel(message, entry.publicId));
80
- textBytes += parts.reduce((total, part) => total + byteLength(part.text), 0);
87
+ textBytes += parts.reduce((total, part) => total + (part.text ? byteLength(part.text) : (part.inlineData ? byteLength(part.inlineData.data) : 0)), 0);
81
88
  if (textBytes > MAX_TEXT_BYTES)
82
89
  fail("Text context is too large.");
83
90
  contents.push({ role: role === "assistant" ? "model" : "user", parts });
@@ -159,6 +166,14 @@ function messagePart(part, assistant, sameProviderAndModel) {
159
166
  fail("Only text context is supported by this provider.");
160
167
  return { text };
161
168
  }
169
+ if (type === "image") {
170
+ const rawData = field(part, "data") ?? (isRecord(field(part, "source")) ? field(field(part, "source"), "data") : undefined);
171
+ if (typeof rawData !== "string" || !rawData)
172
+ fail("Invalid image data.");
173
+ const rawMime = field(part, "mimeType") ?? (isRecord(field(part, "source")) ? field(field(part, "source"), "media_type") : undefined);
174
+ const mimeType = typeof rawMime === "string" && rawMime ? rawMime : "image/jpeg";
175
+ return { inlineData: { mimeType, data: rawData } };
176
+ }
162
177
  fail("Only text context is supported by this provider.");
163
178
  }
164
179
  function isSameProviderAndModel(message, publicId) {
@@ -1,5 +1,5 @@
1
1
  import { canonicalJson, ToolPreflightError } from "./tool-contract.js";
2
- import { GEMINI_PARAMETERS_JSON_SCHEMA_PROFILE } from "./catalog.js";
2
+ import { CLAUDE_CUSTOM_PARAMETERS_PROFILE, GEMINI_PARAMETERS_JSON_SCHEMA_PROFILE } from "./catalog.js";
3
3
  import { normalizeToolDeclarations } from "./tool-schema.js";
4
4
  export function hasToolContext(context) {
5
5
  if (!isRecord(context))
@@ -18,7 +18,7 @@ export function hasToolContext(context) {
18
18
  });
19
19
  }
20
20
  export function prepareToolContext(tools, choice, schemaProfile) {
21
- if (schemaProfile !== GEMINI_PARAMETERS_JSON_SCHEMA_PROFILE)
21
+ if (schemaProfile !== GEMINI_PARAMETERS_JSON_SCHEMA_PROFILE && schemaProfile !== CLAUDE_CUSTOM_PARAMETERS_PROFILE)
22
22
  throw new ToolPreflightError("PI_TOOL_SCHEMA_PROFILE_UNSUPPORTED", "tools", "$");
23
23
  const declarations = normalizeToolDeclarations(tools ?? []);
24
24
  if (!declarations.length) {
@@ -58,13 +58,17 @@ export function replayToolHistory(messages, serializePart, onDiagnostics, policy
58
58
  const finalize = () => {
59
59
  if (!pending)
60
60
  return;
61
- appendTurn("user", pending.calls.map((call) => {
61
+ const turnParts = pending.calls.map((call) => {
62
62
  const actual = pending.results.get(call.id);
63
63
  if (actual)
64
64
  return actual;
65
65
  recoveryCount.value += 1;
66
66
  return pending.observations ? observation(call, missingResultText()) : missingResult(call);
67
- }));
67
+ });
68
+ if (pending.extraParts?.length) {
69
+ turnParts.push(...pending.extraParts);
70
+ }
71
+ appendTurn("user", turnParts);
68
72
  pending = undefined;
69
73
  };
70
74
  for (const message of messages) {
@@ -83,14 +87,34 @@ export function replayToolHistory(messages, serializePart, onDiagnostics, policy
83
87
  history("PI_TOOL_RESULT_NAME_MISMATCH");
84
88
  if (value(current, "addedToolNames") !== undefined && dense(value(current, "addedToolNames")).length)
85
89
  history("PI_TOOL_CALL_INVALID");
86
- const text = dense(value(current, "content")).map((item) => {
90
+ const textParts = [];
91
+ const imageParts = [];
92
+ for (const item of dense(value(current, "content"))) {
87
93
  const part = record(item);
88
- if (value(part, "type") !== "text" || typeof value(part, "text") !== "string")
94
+ if (value(part, "type") === "text" && typeof value(part, "text") === "string") {
95
+ textParts.push(value(part, "text"));
96
+ }
97
+ else if (value(part, "type") === "image") {
98
+ const rawData = value(part, "data") ?? (isRecord(value(part, "source")) ? value(record(value(part, "source")), "data") : undefined);
99
+ if (typeof rawData === "string" && rawData) {
100
+ const rawMime = value(part, "mimeType") ?? (isRecord(value(part, "source")) ? value(record(value(part, "source")), "media_type") : undefined);
101
+ const mimeType = typeof rawMime === "string" && rawMime ? rawMime : "image/jpeg";
102
+ imageParts.push({ inlineData: { mimeType, data: rawData } });
103
+ }
104
+ }
105
+ else {
89
106
  history("PI_TOOL_RESULT_MEDIA_UNSUPPORTED");
90
- return value(part, "text");
91
- }).join("\n\n");
92
- const response = value(current, "isError") === true ? { error: text } : { output: text };
93
- pending.results.set(id, pending.observations ? observation(call, text) : { functionResponse: { name: call.name, response } });
107
+ }
108
+ }
109
+ const text = textParts.join("\n\n");
110
+ const outputText = text || (imageParts.length > 0 ? "[Image content]" : "");
111
+ const response = value(current, "isError") === true ? { error: outputText } : { output: outputText };
112
+ pending.results.set(id, pending.observations ? observation(call, outputText) : { functionResponse: { name: call.name, response } });
113
+ if (imageParts.length > 0) {
114
+ if (!pending.extraParts)
115
+ pending.extraParts = [];
116
+ pending.extraParts.push(...imageParts);
117
+ }
94
118
  results.add(id);
95
119
  continue;
96
120
  }
@@ -5,3 +5,4 @@ export interface ToolDeclaration {
5
5
  readonly parameters: JsonObject;
6
6
  }
7
7
  export declare function normalizeToolDeclarations(value: unknown): readonly ToolDeclaration[];
8
+ export declare function normalizeCustomToolSchema(schema: unknown): JsonObject;
@@ -210,3 +210,34 @@ function isJsonObject(value) {
210
210
  function define(target, key, value) {
211
211
  Object.defineProperty(target, key, { value, enumerable: true, configurable: false, writable: false });
212
212
  }
213
+ const CUSTOM_TOOL_SCHEMA_ALLOW = new Set([
214
+ "type",
215
+ "description",
216
+ "properties",
217
+ "required",
218
+ "items",
219
+ "enum",
220
+ ]);
221
+ export function normalizeCustomToolSchema(schema) {
222
+ if (!schema || typeof schema !== "object" || Array.isArray(schema))
223
+ return {};
224
+ const out = {};
225
+ for (const [key, value] of Object.entries(schema)) {
226
+ if (!CUSTOM_TOOL_SCHEMA_ALLOW.has(key))
227
+ continue;
228
+ if (key === "properties" && value && typeof value === "object" && !Array.isArray(value)) {
229
+ const props = {};
230
+ for (const [propName, propSchema] of Object.entries(value)) {
231
+ props[propName] = normalizeCustomToolSchema(propSchema);
232
+ }
233
+ out.properties = props;
234
+ continue;
235
+ }
236
+ if (key === "items") {
237
+ out.items = normalizeCustomToolSchema(value);
238
+ continue;
239
+ }
240
+ out[key] = value;
241
+ }
242
+ return out;
243
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@benjamolina/pi-antigravity-guard",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "Pi adapter for Antigravity Guard",
5
5
  "type": "module",
6
6
  "license": "MIT",