@opengeni/contracts 1.3.0 → 1.4.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.
@@ -1,7 +1,10 @@
1
1
  import { z } from "zod";
2
2
  export declare const TranscriptionRecordingErrorCode: z.ZodEnum<{
3
+ allowance_exhausted: "allowance_exhausted";
3
4
  cancelled: "cancelled";
5
+ insufficient_credits: "insufficient_credits";
4
6
  invalid_audio: "invalid_audio";
7
+ monthly_model_cost_limit: "monthly_model_cost_limit";
5
8
  network: "network";
6
9
  not_supported: "not_supported";
7
10
  permission_denied: "permission_denied";
@@ -71,8 +74,11 @@ export declare const TranscriptionRecordingSegment: z.ZodObject<{
71
74
  durationMilliseconds: z.ZodNumber;
72
75
  byteLength: z.ZodNumber;
73
76
  errorCode: z.ZodNullable<z.ZodEnum<{
77
+ allowance_exhausted: "allowance_exhausted";
74
78
  cancelled: "cancelled";
79
+ insufficient_credits: "insufficient_credits";
75
80
  invalid_audio: "invalid_audio";
81
+ monthly_model_cost_limit: "monthly_model_cost_limit";
76
82
  network: "network";
77
83
  not_supported: "not_supported";
78
84
  permission_denied: "permission_denied";
@@ -108,8 +114,11 @@ export declare const TranscriptionRecording: z.ZodObject<{
108
114
  transcriptText: z.ZodNullable<z.ZodString>;
109
115
  languages: z.ZodArray<z.ZodString>;
110
116
  errorCode: z.ZodNullable<z.ZodEnum<{
117
+ allowance_exhausted: "allowance_exhausted";
111
118
  cancelled: "cancelled";
119
+ insufficient_credits: "insufficient_credits";
112
120
  invalid_audio: "invalid_audio";
121
+ monthly_model_cost_limit: "monthly_model_cost_limit";
113
122
  network: "network";
114
123
  not_supported: "not_supported";
115
124
  permission_denied: "permission_denied";
@@ -150,8 +159,11 @@ export declare const TranscriptionRecordingResponse: z.ZodObject<{
150
159
  transcriptText: z.ZodNullable<z.ZodString>;
151
160
  languages: z.ZodArray<z.ZodString>;
152
161
  errorCode: z.ZodNullable<z.ZodEnum<{
162
+ allowance_exhausted: "allowance_exhausted";
153
163
  cancelled: "cancelled";
164
+ insufficient_credits: "insufficient_credits";
154
165
  invalid_audio: "invalid_audio";
166
+ monthly_model_cost_limit: "monthly_model_cost_limit";
155
167
  network: "network";
156
168
  not_supported: "not_supported";
157
169
  permission_denied: "permission_denied";
@@ -181,8 +193,11 @@ export declare const TranscriptionRecordingResponse: z.ZodObject<{
181
193
  durationMilliseconds: z.ZodNumber;
182
194
  byteLength: z.ZodNumber;
183
195
  errorCode: z.ZodNullable<z.ZodEnum<{
196
+ allowance_exhausted: "allowance_exhausted";
184
197
  cancelled: "cancelled";
198
+ insufficient_credits: "insufficient_credits";
185
199
  invalid_audio: "invalid_audio";
200
+ monthly_model_cost_limit: "monthly_model_cost_limit";
186
201
  network: "network";
187
202
  not_supported: "not_supported";
188
203
  permission_denied: "permission_denied";
@@ -221,8 +236,11 @@ export declare const TranscriptionRecordingListResponse: z.ZodObject<{
221
236
  transcriptText: z.ZodNullable<z.ZodString>;
222
237
  languages: z.ZodArray<z.ZodString>;
223
238
  errorCode: z.ZodNullable<z.ZodEnum<{
239
+ allowance_exhausted: "allowance_exhausted";
224
240
  cancelled: "cancelled";
241
+ insufficient_credits: "insufficient_credits";
225
242
  invalid_audio: "invalid_audio";
243
+ monthly_model_cost_limit: "monthly_model_cost_limit";
226
244
  network: "network";
227
245
  not_supported: "not_supported";
228
246
  permission_denied: "permission_denied";
@@ -264,8 +282,11 @@ export declare const UploadTranscriptionRecordingChunkResponse: z.ZodObject<{
264
282
  transcriptText: z.ZodNullable<z.ZodString>;
265
283
  languages: z.ZodArray<z.ZodString>;
266
284
  errorCode: z.ZodNullable<z.ZodEnum<{
285
+ allowance_exhausted: "allowance_exhausted";
267
286
  cancelled: "cancelled";
287
+ insufficient_credits: "insufficient_credits";
268
288
  invalid_audio: "invalid_audio";
289
+ monthly_model_cost_limit: "monthly_model_cost_limit";
269
290
  network: "network";
270
291
  not_supported: "not_supported";
271
292
  permission_denied: "permission_denied";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/contracts",
3
- "version": "1.3.0",
3
+ "version": "1.4.2",
4
4
  "description": "Shared zod schemas and wire-contract types for the OpenGeni API.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -423,8 +423,11 @@ export type AgentFunctionToolClass = AgentCapabilityId | "runtime" | "sandbox";
423
423
 
424
424
  /** Tools served outside the first-party MCP catalog, by the name the model sees. */
425
425
  export const AGENT_FUNCTION_TOOL_CAPABILITIES = {
426
- // Hosted provider tools.
426
+ // Web search: the provider-hosted `web_search`/`x_search` tools, or
427
+ // Opengeni's provider-agnostic `web_search` and `web_fetch` function tools
428
+ // (one transport per turn; the capability is the same).
427
429
  web_search: "webSearch",
430
+ web_fetch: "webSearch",
428
431
  x_search: "webSearch",
429
432
  image_generation: "media",
430
433
  // Runtime function tools.
package/src/index.ts CHANGED
@@ -115,6 +115,7 @@ export * from "./editable-artifact-serialized-commit";
115
115
  export * from "./signup-attribution";
116
116
  export * from "./product-lifecycle-facts";
117
117
  export * from "./tool-catalog";
118
+ export * from "./mcp-structured-content";
118
119
  export * from "./mcp-oauth";
119
120
  export * from "./tool-result-spill";
120
121
  export * from "./interaction";
@@ -1864,6 +1865,9 @@ export const TranscriptionErrorCode = z.enum([
1864
1865
  "too_large",
1865
1866
  "invalid_audio",
1866
1867
  "unknown",
1868
+ "insufficient_credits",
1869
+ "allowance_exhausted",
1870
+ "monthly_model_cost_limit",
1867
1871
  ]);
1868
1872
  export type TranscriptionErrorCode = z.infer<typeof TranscriptionErrorCode>;
1869
1873
 
@@ -2120,6 +2124,7 @@ export const VoiceInputProviderId = z.enum([
2120
2124
  "codex-subscription",
2121
2125
  "openai",
2122
2126
  "azure-openai",
2127
+ "azure-mai",
2123
2128
  ]);
2124
2129
  export type VoiceInputProviderId = z.infer<typeof VoiceInputProviderId>;
2125
2130
 
@@ -5016,6 +5021,8 @@ export type PromotionalCreditScope = z.infer<typeof PromotionalCreditScope>;
5016
5021
  export const PromotionalCreditBalance = PromotionalCreditScope.extend({
5017
5022
  grantId: z.string().uuid(),
5018
5023
  remainingMicros: z.number().int().nonnegative(),
5024
+ /** Also pays for dictation and live voice, like general credits (signup credits). */
5025
+ coversVoice: z.boolean().optional(),
5019
5026
  });
5020
5027
  export type PromotionalCreditBalance = z.infer<typeof PromotionalCreditBalance>;
5021
5028
 
@@ -7072,7 +7079,7 @@ export const CodexRealtimeWebrtcResponse = z
7072
7079
  .min(1)
7073
7080
  .max(1024 * 1024),
7074
7081
  version: CodexRealtimeWebrtcVersion,
7075
- model: z.literal("gpt-live-1-boulder-alpha"),
7082
+ model: z.enum(["gpt-live-1-boulder-alpha", "opengeni-azure/gpt-live-1"]),
7076
7083
  connectionId: z.string().uuid(),
7077
7084
  connectionEpoch: z.number().int().positive(),
7078
7085
  startupFenceSequence: z.number().int().nonnegative(),
@@ -7229,6 +7236,7 @@ export const SyncSessionRealtimeLedgerResponse = z
7229
7236
  export type SyncSessionRealtimeLedgerResponse = z.infer<typeof SyncSessionRealtimeLedgerResponse>;
7230
7237
 
7231
7238
  export const SessionRealtimeModel = z.enum([
7239
+ "opengeni-azure/gpt-live-1",
7232
7240
  "gpt-live-1-boulder-alpha",
7233
7241
  "supergrok/grok-voice-think-fast-2.0",
7234
7242
  "opengeni-gateway/openai/gpt-realtime-2.1",
@@ -7247,6 +7255,12 @@ export const WorkspaceRealtimeModelCatalogItem = z.object({
7247
7255
  description: z.string().min(1),
7248
7256
  available: z.boolean(),
7249
7257
  unavailableReason: z.string().nullable(),
7258
+ /**
7259
+ * Machine-readable reason when unavailable. Credit refusals use the same
7260
+ * codes as voice input (`insufficient_credits`, `allowance_exhausted`,
7261
+ * `monthly_model_cost_limit`).
7262
+ */
7263
+ unavailableCode: z.string().min(1).max(64).nullable().optional(),
7250
7264
  recommended: z.boolean(),
7251
7265
  });
7252
7266
  export type WorkspaceRealtimeModelCatalogItem = z.infer<typeof WorkspaceRealtimeModelCatalogItem>;
@@ -7302,9 +7316,21 @@ export const EndSessionRealtimeRequest = RenewSessionRealtimeRequest.extend({
7302
7316
  });
7303
7317
  export type EndSessionRealtimeRequest = z.infer<typeof EndSessionRealtimeRequest>;
7304
7318
 
7319
+ /**
7320
+ * Server instruction to end a live call now. Deployment-funded voice returns
7321
+ * it from a heartbeat when credits run out; the lease is then no longer
7322
+ * extended, so the client should drain and end the call gracefully.
7323
+ */
7324
+ export const SessionRealtimeStopInstruction = z.object({
7325
+ code: z.string().min(1).max(64),
7326
+ message: z.string().min(1).max(512),
7327
+ });
7328
+ export type SessionRealtimeStopInstruction = z.infer<typeof SessionRealtimeStopInstruction>;
7329
+
7305
7330
  export const SessionRealtimeMutationResponse = z.object({
7306
7331
  mode: SessionRealtimeMode,
7307
7332
  replay: z.boolean(),
7333
+ stop: SessionRealtimeStopInstruction.optional(),
7308
7334
  });
7309
7335
  export type SessionRealtimeMutationResponse = z.infer<typeof SessionRealtimeMutationResponse>;
7310
7336
 
@@ -12082,6 +12108,11 @@ export const CapabilityRuntime = z.object({
12082
12108
  ]),
12083
12109
  })
12084
12110
  .optional(),
12111
+ // Server-derived connectability: present when the connector's OAuth
12112
+ // authorization server refuses self-registration, so connecting needs an
12113
+ // operator-registered client. `configured` is whether this deployment has
12114
+ // one; connector surfaces offer the row only when it is true.
12115
+ operatorOAuthClient: z.object({ configured: z.boolean() }).optional(),
12085
12116
  });
12086
12117
  export type CapabilityRuntime = z.infer<typeof CapabilityRuntime>;
12087
12118
 
@@ -0,0 +1,81 @@
1
+ import type { AttemptToolResult } from "./tool-catalog";
2
+
3
+ /**
4
+ * Remove the backwards-compatibility text copy of an MCP result's
5
+ * `structuredContent`.
6
+ *
7
+ * The MCP specification asks a server that returns `structuredContent` to also
8
+ * return the same value serialized in a text content block. Printing or sending
9
+ * both pays for the payload twice, and the text copy pays again for JSON string
10
+ * escaping inside the result envelope.
11
+ *
12
+ * The envelope (`content`, `structuredContent`, `_meta`, `isError`, extension
13
+ * fields) and its key order are kept. Only a plain `{type: "text", text}` block
14
+ * whose text parses as JSON to a value equal to `structuredContent` is removed.
15
+ * Prose, differing JSON, annotated text, images, resources, and every other
16
+ * block stay. A text block whose JSON carries an integer outside the IEEE-754
17
+ * safe range also stays, because its digits may be more precise than the
18
+ * parsed `structuredContent`. Numbers compare by IEEE-754 value.
19
+ *
20
+ * Results without a removable block are returned as the same object.
21
+ *
22
+ * Callers: the runtime's model-facing MCP projection, and the default
23
+ * `ogtool call` print. The native Connected Machine client implements the
24
+ * same rule in `agent/crates/opengeni-agent/src/codemode.rs`; keep them equal.
25
+ */
26
+ export function omitStructuredContentTextDuplicates<T extends AttemptToolResult>(result: T): T {
27
+ const structured: unknown = result.structuredContent;
28
+ if (structured === undefined || structured === null) return result;
29
+ const content = result.content.filter(
30
+ (entry) => !isPlainTextBlock(entry) || !textDuplicatesStructuredContent(entry.text, structured),
31
+ );
32
+ if (content.length === result.content.length) return result;
33
+ return { ...result, content };
34
+ }
35
+
36
+ function isPlainTextBlock(entry: unknown): entry is { type: "text"; text: string } {
37
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return false;
38
+ const record = entry as Record<string, unknown>;
39
+ if (record.type !== "text" || typeof record.text !== "string") return false;
40
+ // Annotations or _meta are block-level facts the structured value lacks.
41
+ return Object.keys(record).every((key) => key === "type" || key === "text");
42
+ }
43
+
44
+ function textDuplicatesStructuredContent(text: string, structured: unknown): boolean {
45
+ const trimmed = text.replace(/^[ \t\n\r]+/u, "");
46
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return false;
47
+ let parsed: unknown;
48
+ try {
49
+ parsed = JSON.parse(text);
50
+ } catch {
51
+ return false;
52
+ }
53
+ return !containsUnsafeInteger(parsed) && jsonValuesEqual(parsed, structured);
54
+ }
55
+
56
+ function containsUnsafeInteger(value: unknown): boolean {
57
+ if (typeof value === "number") return Number.isInteger(value) && !Number.isSafeInteger(value);
58
+ if (Array.isArray(value)) return value.some(containsUnsafeInteger);
59
+ if (value && typeof value === "object") return Object.values(value).some(containsUnsafeInteger);
60
+ return false;
61
+ }
62
+
63
+ function jsonValuesEqual(left: unknown, right: unknown): boolean {
64
+ if (left === right) return true;
65
+ if (typeof left !== "object" || typeof right !== "object" || left === null || right === null) {
66
+ return false;
67
+ }
68
+ if (Array.isArray(left) || Array.isArray(right)) {
69
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
70
+ return false;
71
+ }
72
+ return left.every((entry, index) => jsonValuesEqual(entry, right[index]));
73
+ }
74
+ const leftRecord = left as Record<string, unknown>;
75
+ const rightRecord = right as Record<string, unknown>;
76
+ const leftKeys = Object.keys(leftRecord);
77
+ if (leftKeys.length !== Object.keys(rightRecord).length) return false;
78
+ return leftKeys.every(
79
+ (key) => Object.hasOwn(rightRecord, key) && jsonValuesEqual(leftRecord[key], rightRecord[key]),
80
+ );
81
+ }
@@ -174,6 +174,7 @@ export const ToolReviewStatus = z.enum([
174
174
  "cancelled",
175
175
  "expired",
176
176
  "revoked",
177
+ "blocked",
177
178
  "stale",
178
179
  "failed",
179
180
  "unavailable",
@@ -14,7 +14,17 @@ export function toolReviewFieldLabel(key: string): string {
14
14
  const label = key
15
15
  .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
16
16
  .replace(/[_-]+/g, " ")
17
- .replace(/\bids?\b/gi, (word) => (word.length === 3 ? "IDs" : "ID"));
17
+ .replace(/\bids?\b/gi, (word) => (word.length === 3 ? "IDs" : "ID"))
18
+ .replace(
19
+ /\b(api|url|uri|http|https|ip|sql|json|html)(s?)\b/gi,
20
+ (_word, name: string, plural: string) => `${name.toUpperCase()}${plural}`,
21
+ )
22
+ // Sentence case, keeping acronyms: "Working Directory" → "Working directory".
23
+ .replace(
24
+ /(?<=\S\s+)([A-Z])([a-z]+)\b/g,
25
+ (_word, first: string, rest: string) => `${first.toLowerCase()}${rest}`,
26
+ )
27
+ .trim();
18
28
  return label.charAt(0).toUpperCase() + label.slice(1);
19
29
  }
20
30
  export function decodeReviewArguments(value: unknown): Record<string, unknown> | null {
@@ -85,8 +95,35 @@ export function toolReviewFields(
85
95
  (field) =>
86
96
  field.path !== "/contentSha256" && field.path !== "/htmlBody" && field.preview !== "",
87
97
  )
88
- .map((field) => ({ ...field, path: `/$email${field.path}` })),
89
- ...original.fields,
98
+ .map((field) => ({
99
+ ...field,
100
+ label: field.path === "/textBody" ? "Message" : field.label,
101
+ path: `/$email${field.path}`,
102
+ ...(field.path === "/attachments"
103
+ ? {
104
+ preview: context
105
+ .email!.attachments.map((attachment) => attachment.name)
106
+ .join(", ")
107
+ .slice(0, 240),
108
+ }
109
+ : {}),
110
+ })),
111
+ ...original.fields.filter(
112
+ (field) =>
113
+ ![
114
+ "/raw",
115
+ "/expectedContentSha256",
116
+ "/from",
117
+ "/to",
118
+ "/cc",
119
+ "/bcc",
120
+ "/subject",
121
+ "/body",
122
+ "/textBody",
123
+ "/htmlBody",
124
+ "/attachments",
125
+ ].includes(field.path),
126
+ ),
90
127
  ];
91
128
  return {
92
129
  fields: fields.slice(0, 16),
@@ -98,13 +135,26 @@ export function toolReviewFields(
98
135
  fields: entries.slice(0, 16).map(([key, value], index) => {
99
136
  const hidden = protectedReviewField(key, context?.protectedFields);
100
137
  const safe = hidden ? "[Protected value]" : value;
101
- const count = Array.isArray(value) ? value.length : undefined;
138
+ const count = !hidden && Array.isArray(value) ? value.length : undefined;
139
+ const compactList =
140
+ !hidden &&
141
+ Array.isArray(value) &&
142
+ value.length > 0 &&
143
+ value.length <= 4 &&
144
+ value.every(
145
+ (entry) =>
146
+ typeof entry === "string" || typeof entry === "number" || typeof entry === "boolean",
147
+ )
148
+ ? value.map(valueText).join(", ")
149
+ : undefined;
102
150
  const text =
103
- count !== undefined
104
- ? `${count.toLocaleString("en-US")} item${count === 1 ? "" : "s"}`
105
- : safe && typeof safe === "object"
106
- ? `${Object.keys(safe).length} field${Object.keys(safe).length === 1 ? "" : "s"}`
107
- : valueText(safe);
151
+ compactList !== undefined
152
+ ? compactList
153
+ : count !== undefined
154
+ ? `${count.toLocaleString("en-US")} item${count === 1 ? "" : "s"}`
155
+ : safe && typeof safe === "object"
156
+ ? `${Object.keys(safe).length} field${Object.keys(safe).length === 1 ? "" : "s"}`
157
+ : valueText(safe);
108
158
  return {
109
159
  path: fieldPointer(key, index),
110
160
  label: bound(
@@ -115,7 +165,10 @@ export function toolReviewFields(
115
165
  ),
116
166
  preview: bound(text, 240),
117
167
  ...(count !== undefined ? { count } : {}),
118
- truncated: !hidden && (text.length > 240 || (typeof value === "object" && value !== null)),
168
+ truncated:
169
+ !hidden &&
170
+ (text.length > 240 ||
171
+ (compactList === undefined && typeof value === "object" && value !== null)),
119
172
  ...(hidden ? { protected: true } : {}),
120
173
  };
121
174
  }),
@@ -132,7 +185,14 @@ export function toolReviewAction(
132
185
  "title" | "approveLabel" | "effects" | "consequence" | "selectionCount" | "selectionKind"
133
186
  > {
134
187
  const fallback = {
135
- title: context?.title ?? bound(toolReviewFieldLabel(toolName.replace(/^mcp_[^_]+__/, "")), 256),
188
+ title:
189
+ context?.title ??
190
+ bound(
191
+ toolReviewFieldLabel(
192
+ toolName.replace(/^mcp_[^_]+__/, "").replace(/^batch[_-](?=[a-z])/i, ""),
193
+ ),
194
+ 256,
195
+ ),
136
196
  approveLabel: "Approve action",
137
197
  effects: [] as string[],
138
198
  };
@@ -152,8 +212,10 @@ export function toolReviewAction(
152
212
  const selectionCount = ids?.every((id) => typeof id === "string") ? new Set(ids).size : undefined;
153
213
  const noun =
154
214
  selectionCount === undefined
155
- ? selectionKind
156
- : `${selectionCount.toLocaleString("en-US")} ${thread ? "thread" : "message"}${selectionCount === 1 ? "" : "s"}`;
215
+ ? thread
216
+ ? "conversations"
217
+ : "messages"
218
+ : `${selectionCount.toLocaleString("en-US")} ${thread ? "conversation" : "message"}${selectionCount === 1 ? "" : "s"}`;
157
219
  const addValues = toolName.startsWith("label_") ? args.labelIds : args.addLabelIds;
158
220
  const adds = Array.isArray(addValues)
159
221
  ? addValues.filter((v): v is string => typeof v === "string")
@@ -200,7 +262,7 @@ export function toolReviewAction(
200
262
  ...(trash
201
263
  ? { consequence: "Messages stay recoverable in Trash until Gmail deletes them." }
202
264
  : thread
203
- ? { consequence: "Applies to every message in the selected thread." }
265
+ ? { consequence: "Applies to every message in the conversation." }
204
266
  : {}),
205
267
  };
206
268
  }
@@ -12,6 +12,9 @@ export const TranscriptionRecordingErrorCode = z.enum([
12
12
  "too_large",
13
13
  "invalid_audio",
14
14
  "unknown",
15
+ "insufficient_credits",
16
+ "allowance_exhausted",
17
+ "monthly_model_cost_limit",
15
18
  ]);
16
19
  export type TranscriptionRecordingErrorCode = z.infer<typeof TranscriptionRecordingErrorCode>;
17
20