Skip to content

Commit 7e5241c

Browse files
committed
feat(dev-ui): add ui component metadata
1 parent a582547 commit 7e5241c

9 files changed

Lines changed: 151 additions & 34 deletions

File tree

js/core/src/error.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,13 @@
1515
*/
1616

1717
import type { Registry } from './registry.js';
18-
import { httpStatusCode, type StatusName } from './statusTypes.js';
18+
import {
19+
StatusNameSchema,
20+
httpStatusCode,
21+
type StatusName,
22+
} from './statusTypes.js';
1923

24+
export { StatusNameSchema };
2025
export type { StatusName };
2126

2227
export interface HttpErrorWireFormat {

js/core/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export {
6565
} from './dynamic-action-provider.js';
6666
export {
6767
GenkitError,
68+
StatusNameSchema,
6869
UnstableApiError,
6970
UserFacingError,
7071
assertUnstable,
@@ -91,6 +92,11 @@ export {
9192
toJsonSchema,
9293
type JSONSchema,
9394
} from './schema.js';
95+
export {
96+
GENKIT_UI_WIDGETS,
97+
GENKIT_UI_METADATA,
98+
type GenkitUiWidget,
99+
} from './metadata.js';
94100
export * from './telemetryTypes.js';
95101
export * from './utils.js';
96102

js/core/src/metadata.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Metadata keys used for UI-specific schema annotations.
19+
*/
20+
export const GENKIT_UI_METADATA = {
21+
/**
22+
* Provides data to populate both standard and customized UI widgets.
23+
*
24+
* e.g.
25+
*
26+
* {
27+
* [GENKIT_UI_METADATA.DATA_SOURCE]: {
28+
* action: '/custom/foo',
29+
* allowCustomValues: true
30+
* }
31+
* }
32+
*/
33+
DATA_SOURCE: 'x-genkit-ui-data-source',
34+
35+
/**
36+
* Provides a display name or label for the UI. By default, the Dev UI will
37+
* transform schema fields to "Sentence case". There are times where this is
38+
* not desirable and needs to be overwritten, such as abbreviations or
39+
* acronyms (e.g. "Top P", "Top K") and proper nouns (e.g. "Google Search
40+
* retrieval").
41+
*
42+
* e.g. { [GENKIT_UI_METADATA.DISPLAY_NAME]: 'Top P' }
43+
*/
44+
DISPLAY_NAME: 'x-genkit-ui-display-name',
45+
46+
/**
47+
* Specifies the UI component (widget) to use for a schema field. Commonly
48+
* used for model and middleware configuration. Useful to resolve ambiguity
49+
* when multiple inputs could apply, or to provide a tailored user experience
50+
* for complex inputs.
51+
*
52+
* e.g. { [GENKIT_UI_METADATA.WIDGET]: GENKIT_UI_WIDGETS.MODEL_LIST }
53+
*/
54+
WIDGET: 'x-genkit-ui-widget',
55+
} as const;
56+
57+
/**
58+
* Standard UI widget names used with GENKIT_UI_METADATA.WIDGET.
59+
*/
60+
export const GENKIT_UI_WIDGETS = {
61+
/** A widget for configuring a list of models. */
62+
MODEL_LIST: 'model-list-config',
63+
/** A widget for configuring LLM model safety settings. */
64+
SAFETY_SETTINGS: 'safety-settings',
65+
} as const;
66+
67+
/**
68+
* Union type of standard Genkit UI widget names.
69+
*/
70+
export type GenkitUiWidget =
71+
(typeof GENKIT_UI_WIDGETS)[keyof typeof GENKIT_UI_WIDGETS];

js/core/tests/schema_test.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Ajv from 'ajv';
1818
import * as assert from 'assert';
1919
import { afterEach, describe, it, mock } from 'node:test';
2020

21+
import { GENKIT_UI_METADATA } from '../src/metadata.js';
2122
import { setGenkitRuntimeConfig } from '../src/config.js';
2223
import {
2324
ValidationError,
@@ -169,48 +170,48 @@ describe('toJsonSchema', () => {
169170
describe('annotateSchema()', () => {
170171
it('should merge annotations into the JSON schema', () => {
171172
const schema = annotateSchema(z.string(), {
172-
'x-genkit-data-source': 'my-action',
173+
[GENKIT_UI_METADATA.DATA_SOURCE]: 'my-action',
173174
});
174175

175176
const json = toJsonSchema({ schema });
176-
assert.strictEqual(json['x-genkit-data-source'], 'my-action');
177+
assert.strictEqual(json[GENKIT_UI_METADATA.DATA_SOURCE], 'my-action');
177178
});
178179

179180
it('should merge annotations for nested fields', () => {
180181
const schema = z.object({
181182
field: annotateSchema(z.string(), {
182-
'x-genkit-data-source': 'nested-action',
183+
[GENKIT_UI_METADATA.DATA_SOURCE]: 'nested-action',
183184
}),
184185
});
185186

186187
const json = toJsonSchema({ schema });
187188
assert.strictEqual(
188-
json.properties.field['x-genkit-data-source'],
189+
json.properties.field[GENKIT_UI_METADATA.DATA_SOURCE],
189190
'nested-action'
190191
);
191192
});
192193

193194
it('should merge annotations for array items', () => {
194195
const schema = z.array(
195196
annotateSchema(z.string(), {
196-
'x-genkit-data-source': 'array-action',
197+
[GENKIT_UI_METADATA.DATA_SOURCE]: 'array-action',
197198
})
198199
);
199200

200201
const json = toJsonSchema({ schema });
201-
assert.strictEqual(json.items['x-genkit-data-source'], 'array-action');
202+
assert.strictEqual(json.items[GENKIT_UI_METADATA.DATA_SOURCE], 'array-action');
202203
});
203204

204205
it('should merge annotations for optional fields', () => {
205206
const schema = z.object({
206207
field: annotateSchema(z.string(), {
207-
'x-genkit-data-source': 'optional-action',
208+
[GENKIT_UI_METADATA.DATA_SOURCE]: 'optional-action',
208209
}).optional(),
209210
});
210211

211212
const json = toJsonSchema({ schema });
212213
assert.strictEqual(
213-
json.properties.field['x-genkit-data-source'],
214+
json.properties.field[GENKIT_UI_METADATA.DATA_SOURCE],
214215
'optional-action'
215216
);
216217
});

js/genkit/src/common.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,11 @@ export {
136136
OperationSchema,
137137
ReflectionServer,
138138
StatusCodes,
139+
StatusNameSchema,
139140
StatusSchema,
140141
UserFacingError,
142+
GENKIT_UI_WIDGETS,
143+
GENKIT_UI_METADATA,
141144
annotateSchema,
142145
defineJsonSchema,
143146
defineSchema,
@@ -160,6 +163,7 @@ export {
160163
type FlowFn,
161164
type FlowSideChannel,
162165
type GenkitRuntimeConfig,
166+
type GenkitUiWidget,
163167
type JSONSchema,
164168
type JSONSchema7,
165169
type Middleware,

js/plugins/google-genai/src/googleai/gemini.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,25 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { ActionMetadata, GenkitError, modelActionMetadata, z } from 'genkit';
17+
import {
18+
ActionMetadata,
19+
annotateSchema,
20+
GENKIT_UI_WIDGETS,
21+
GENKIT_UI_METADATA,
22+
GenkitError,
23+
modelActionMetadata,
24+
z,
25+
} from 'genkit';
1826
import {
1927
CandidateData,
2028
GenerationCommonConfigDescriptions,
2129
GenerationCommonConfigSchema,
30+
getBasicUsageStats,
2231
ModelAction,
2332
ModelInfo,
2433
ModelMiddleware,
25-
ModelReference,
26-
getBasicUsageStats,
2734
modelRef,
35+
ModelReference,
2836
} from 'genkit/model';
2937
import { downloadRequestMedia } from 'genkit/model/middleware';
3038
import { model as pluginModel } from 'genkit/plugin';
@@ -154,13 +162,16 @@ export const GeminiConfigSchema = GenerationCommonConfigSchema.extend({
154162
'Overrides the plugin-configured or default apiVersion, if specified.'
155163
)
156164
.optional(),
157-
safetySettings: z
158-
.array(SafetySettingsSchema)
159-
.describe(
160-
'Adjust how likely you are to see responses that could be harmful. ' +
161-
'Content is blocked based on the probability that it is harmful.'
162-
)
163-
.optional(),
165+
safetySettings: annotateSchema(
166+
z
167+
.array(SafetySettingsSchema)
168+
.describe(
169+
'Adjust how likely you are to see responses that could be harmful. ' +
170+
'Content is blocked based on the probability that it is harmful.'
171+
)
172+
.optional(),
173+
{ [GENKIT_UI_METADATA.WIDGET]: GENKIT_UI_WIDGETS.SAFETY_SETTINGS }
174+
),
164175
codeExecution: z
165176
.union([z.boolean(), z.object({}).strict()])
166177
.describe('Enables the model to generate and run code.')

js/plugins/google-genai/src/vertexai/gemini.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,25 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { ActionMetadata, GenkitError, modelActionMetadata, z } from 'genkit';
17+
import {
18+
ActionMetadata,
19+
annotateSchema,
20+
GENKIT_UI_WIDGETS,
21+
GENKIT_UI_METADATA,
22+
GenkitError,
23+
modelActionMetadata,
24+
z,
25+
} from 'genkit';
1826
import {
1927
CandidateData,
2028
GenerationCommonConfigDescriptions,
2129
GenerationCommonConfigSchema,
30+
getBasicUsageStats,
2231
ModelAction,
2332
ModelInfo,
2433
ModelMiddleware,
25-
ModelReference,
26-
getBasicUsageStats,
2734
modelRef,
35+
ModelReference,
2836
} from 'genkit/model';
2937
import { downloadRequestMedia } from 'genkit/model/middleware';
3038
import { model as pluginModel } from 'genkit/plugin';
@@ -185,13 +193,16 @@ export const GeminiConfigSchema = GenerationCommonConfigSchema.extend({
185193
* }
186194
* ```
187195
*/
188-
safetySettings: z
189-
.array(SafetySettingsSchema)
190-
.describe(
191-
'Adjust how likely you are to see responses that could be harmful. ' +
192-
'Content is blocked based on the probability that it is harmful.'
193-
)
194-
.optional(),
196+
safetySettings: annotateSchema(
197+
z
198+
.array(SafetySettingsSchema)
199+
.describe(
200+
'Adjust how likely you are to see responses that could be harmful. ' +
201+
'Content is blocked based on the probability that it is harmful.'
202+
)
203+
.optional(),
204+
{ [GENKIT_UI_METADATA.WIDGET]: GENKIT_UI_WIDGETS.SAFETY_SETTINGS }
205+
),
195206

196207
/**
197208
* Vertex retrieval options.

js/plugins/middleware/src/fallback.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@
1515
*/
1616

1717
import {
18+
GENKIT_UI_WIDGETS,
19+
GENKIT_UI_METADATA,
1820
GenkitError,
1921
ModelReferenceSchema,
22+
StatusNameSchema,
23+
annotateSchema,
2024
generateMiddleware,
2125
z,
2226
type GenerateMiddleware,
@@ -40,15 +44,18 @@ export const FallbackOptionsSchema = z
4044
/**
4145
* An array of models to try in order.
4246
*/
43-
models: z
44-
.array(ModelReferenceSchema)
45-
.describe('An array of models to try in order.'),
47+
models: annotateSchema(
48+
z
49+
.array(ModelReferenceSchema)
50+
.describe('An array of models to try in order.'),
51+
{ [GENKIT_UI_METADATA.WIDGET]: GENKIT_UI_WIDGETS.MODEL_LIST }
52+
),
4653
/**
4754
* An array of `StatusName` values that should trigger a fallback.
4855
* @default ['UNAVAILABLE', 'DEADLINE_EXCEEDED', 'RESOURCE_EXHAUSTED', 'ABORTED', 'INTERNAL', 'NOT_FOUND', 'UNIMPLEMENTED']
4956
*/
5057
statuses: z
51-
.array(z.string())
58+
.array(StatusNameSchema)
5259
.optional()
5360
.describe(
5461
'An array of StatusName values that should trigger a fallback.'

js/plugins/middleware/src/retry.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import {
1818
GenerateMiddleware,
1919
GenkitError,
20+
StatusNameSchema,
2021
generateMiddleware,
2122
z,
2223
type StatusName,
@@ -38,7 +39,7 @@ export const RetryOptionsSchema = z
3839
* @default ['UNAVAILABLE', 'DEADLINE_EXCEEDED', 'RESOURCE_EXHAUSTED', 'ABORTED', 'INTERNAL']
3940
*/
4041
statuses: z
41-
.array(z.string())
42+
.array(StatusNameSchema)
4243
.optional()
4344
.describe('An array of StatusName values that should trigger a retry.'),
4445
/**

0 commit comments

Comments
 (0)