@mccaskie-labs/workbench-config 0.1.1 → 0.1.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 +6 -0
- package/capabilities/image-search.json +130 -0
- package/capabilities/image-search.md +19 -0
- package/capabilities/images.json +208 -0
- package/capabilities/images.md +27 -0
- package/dist/capabilities.gen.d.ts +246 -1
- package/dist/capabilities.gen.js +339 -1
- package/dist/capability-schema.d.ts +25 -0
- package/dist/capability-schema.js +13 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./schema.json",
|
|
3
|
+
"format": 1,
|
|
4
|
+
"component": "image-search",
|
|
5
|
+
"title": "Image search through the Gateway",
|
|
6
|
+
"summary": "Web image search through Serper, normalised to one result shape and metered against the project's budget. Apps never hold a Serper key.",
|
|
7
|
+
"status": "planned",
|
|
8
|
+
"surfaces": {
|
|
9
|
+
"gateway": [
|
|
10
|
+
"POST /v1/images/search"
|
|
11
|
+
],
|
|
12
|
+
"package": [
|
|
13
|
+
"Ai.images.search"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"config": [
|
|
17
|
+
{
|
|
18
|
+
"name": "WORKBENCH_GATEWAY_KEY",
|
|
19
|
+
"required": true,
|
|
20
|
+
"secret": true,
|
|
21
|
+
"environments": [
|
|
22
|
+
"staging",
|
|
23
|
+
"production"
|
|
24
|
+
],
|
|
25
|
+
"description": "Project key with the search scope, delivered as a Worker secret by gw_key_issue"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"allowed": {
|
|
29
|
+
"providers": [
|
|
30
|
+
"serper"
|
|
31
|
+
],
|
|
32
|
+
"result_fields": [
|
|
33
|
+
"title",
|
|
34
|
+
"image_url",
|
|
35
|
+
"thumbnail_url",
|
|
36
|
+
"width",
|
|
37
|
+
"height",
|
|
38
|
+
"source",
|
|
39
|
+
"page_url"
|
|
40
|
+
],
|
|
41
|
+
"required_headers": [
|
|
42
|
+
"X-Workbench-Job"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"limits": [
|
|
46
|
+
{
|
|
47
|
+
"id": "image-search.num",
|
|
48
|
+
"value": "1-100",
|
|
49
|
+
"unit": "results",
|
|
50
|
+
"per": "request",
|
|
51
|
+
"enforced_by": "gateway",
|
|
52
|
+
"refusal": "invalid_request"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "image-search.num-default",
|
|
56
|
+
"value": 10,
|
|
57
|
+
"unit": "results",
|
|
58
|
+
"per": "request",
|
|
59
|
+
"enforced_by": "gateway"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "image-search.upstream-timeout",
|
|
63
|
+
"value": 90,
|
|
64
|
+
"unit": "seconds",
|
|
65
|
+
"per": "request",
|
|
66
|
+
"enforced_by": "gateway",
|
|
67
|
+
"refusal": "upstream_timeout"
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
"errors": [
|
|
71
|
+
{
|
|
72
|
+
"code": "invalid_request",
|
|
73
|
+
"http": 400,
|
|
74
|
+
"retryable": false,
|
|
75
|
+
"meaning": "An empty query or a bad num, gl or hl"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"code": "budget_exceeded",
|
|
79
|
+
"http": 402,
|
|
80
|
+
"retryable": false,
|
|
81
|
+
"meaning": "Enforced monthly budget reached"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"code": "upstream_error",
|
|
85
|
+
"http": 502,
|
|
86
|
+
"retryable": true,
|
|
87
|
+
"meaning": "Serper failed after the Gateway's retries"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"code": "upstream_timeout",
|
|
91
|
+
"http": 504,
|
|
92
|
+
"retryable": true,
|
|
93
|
+
"meaning": "Serper didn't answer within 90 seconds"
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
"rules": [
|
|
97
|
+
{
|
|
98
|
+
"id": "image-search.https-only",
|
|
99
|
+
"kind": "must",
|
|
100
|
+
"text": "Use only https image and page URLs from the results; the Gateway drops any result without one.",
|
|
101
|
+
"enforced_by": "gateway",
|
|
102
|
+
"probe": "image-search.serper"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"id": "image-search.licence-check",
|
|
106
|
+
"kind": "must",
|
|
107
|
+
"text": "Treat results as leads, not as images the app may republish. Check the source's licence before storing or showing one.",
|
|
108
|
+
"enforced_by": "review",
|
|
109
|
+
"probe": "image-search.serper"
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
"examples": [
|
|
113
|
+
{
|
|
114
|
+
"id": "image-search.example.search",
|
|
115
|
+
"title": "Search for images",
|
|
116
|
+
"lang": "ts",
|
|
117
|
+
"probe": "image-search.serper",
|
|
118
|
+
"code": "const ai = createAi({ key: env.WORKBENCH_GATEWAY_KEY, env: workbenchEnv(env) })\nconst results = await ai.images.search({ q: 'Mayon volcano', job: 'editor.image-search', num: 10 })"
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
"probes": [
|
|
122
|
+
{
|
|
123
|
+
"id": "image-search.serper",
|
|
124
|
+
"half": "runtime",
|
|
125
|
+
"expect": "pass",
|
|
126
|
+
"smoke": true,
|
|
127
|
+
"cost_hint_usd": 0.001
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Image search
|
|
2
|
+
|
|
3
|
+
Apps search the web for images through the Gateway with `ai.images.search` from `@mccaskie-labs/workbench-ai`, or `POST /v1/images/search` directly. The Gateway holds the Serper key; the app's Gateway key needs the `search` scope.
|
|
4
|
+
|
|
5
|
+
## Request
|
|
6
|
+
|
|
7
|
+
`q` is required. `num` is 1 to 100 and defaults to 10. `gl` (country) and `hl` (language) pass through to Serper, for example `gl: 'ph', hl: 'en'`.
|
|
8
|
+
|
|
9
|
+
## Results
|
|
10
|
+
|
|
11
|
+
Each result is `{ title, image_url, thumbnail_url, width, height, source, page_url }`. Results without an https image URL are dropped. Filtering by size, shape or site is the app's job: ask for more results than you need and filter them.
|
|
12
|
+
|
|
13
|
+
## Cost
|
|
14
|
+
|
|
15
|
+
Serper bills in credits of US$0.001: 1 credit for up to 10 results, 2 for more. Only searches Serper answers are recorded as usage.
|
|
16
|
+
|
|
17
|
+
## Licences
|
|
18
|
+
|
|
19
|
+
A result is a lead, not permission. Check the source's licence before the app stores or shows an image it found.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./schema.json",
|
|
3
|
+
"format": 1,
|
|
4
|
+
"component": "images",
|
|
5
|
+
"title": "Image generation through the Gateway",
|
|
6
|
+
"summary": "One image per call from Gemini or OpenAI, chosen by a Workbench alias and metered against the project's budget. Apps never hold a Google or OpenAI key, and the Gateway never falls back to another provider.",
|
|
7
|
+
"status": "planned",
|
|
8
|
+
"surfaces": {
|
|
9
|
+
"gateway": [
|
|
10
|
+
"POST /v1/images/generate"
|
|
11
|
+
],
|
|
12
|
+
"package": [
|
|
13
|
+
"Ai.images.generate"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"config": [
|
|
17
|
+
{
|
|
18
|
+
"name": "WORKBENCH_GATEWAY_KEY",
|
|
19
|
+
"required": true,
|
|
20
|
+
"secret": true,
|
|
21
|
+
"environments": [
|
|
22
|
+
"staging",
|
|
23
|
+
"production"
|
|
24
|
+
],
|
|
25
|
+
"description": "Project key with the images scope, delivered as a Worker secret by gw_key_issue"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"allowed": {
|
|
29
|
+
"aliases": [
|
|
30
|
+
{
|
|
31
|
+
"alias": "image-default",
|
|
32
|
+
"provider": "google",
|
|
33
|
+
"default_model": "gemini-3.1-flash-image"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"alias": "image-pro",
|
|
37
|
+
"provider": "google",
|
|
38
|
+
"default_model": "gemini-3-pro-image"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"alias": "image-openai",
|
|
42
|
+
"provider": "openai",
|
|
43
|
+
"default_model": "gpt-image-2.5-sunburst"
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"aspect_ratios": [
|
|
47
|
+
"1:1",
|
|
48
|
+
"3:2",
|
|
49
|
+
"2:3",
|
|
50
|
+
"4:3",
|
|
51
|
+
"3:4",
|
|
52
|
+
"16:9",
|
|
53
|
+
"9:16"
|
|
54
|
+
],
|
|
55
|
+
"reference_providers": [
|
|
56
|
+
"google"
|
|
57
|
+
],
|
|
58
|
+
"reference_mime_types": [
|
|
59
|
+
"image/png",
|
|
60
|
+
"image/jpeg",
|
|
61
|
+
"image/webp"
|
|
62
|
+
],
|
|
63
|
+
"output_mime_types": [
|
|
64
|
+
"image/png",
|
|
65
|
+
"image/jpeg"
|
|
66
|
+
],
|
|
67
|
+
"required_headers": [
|
|
68
|
+
"X-Workbench-Job"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
"limits": [
|
|
72
|
+
{
|
|
73
|
+
"id": "images.per-call",
|
|
74
|
+
"value": 1,
|
|
75
|
+
"unit": "images",
|
|
76
|
+
"per": "request",
|
|
77
|
+
"enforced_by": "gateway"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "images.request-size",
|
|
81
|
+
"value": 20971520,
|
|
82
|
+
"unit": "bytes",
|
|
83
|
+
"per": "request",
|
|
84
|
+
"enforced_by": "gateway",
|
|
85
|
+
"refusal": "invalid_request"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": "images.upstream-timeout",
|
|
89
|
+
"value": 240,
|
|
90
|
+
"unit": "seconds",
|
|
91
|
+
"per": "request",
|
|
92
|
+
"enforced_by": "gateway",
|
|
93
|
+
"refusal": "upstream_timeout"
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
"errors": [
|
|
97
|
+
{
|
|
98
|
+
"code": "model_not_allowed",
|
|
99
|
+
"http": 400,
|
|
100
|
+
"retryable": false,
|
|
101
|
+
"meaning": "Not an image alias"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"code": "invalid_request",
|
|
105
|
+
"http": 400,
|
|
106
|
+
"retryable": false,
|
|
107
|
+
"meaning": "A bad aspect ratio, prompt or reference, or a reference on an OpenAI model"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"code": "budget_exceeded",
|
|
111
|
+
"http": 402,
|
|
112
|
+
"retryable": false,
|
|
113
|
+
"meaning": "Enforced monthly budget reached"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"code": "upstream_error",
|
|
117
|
+
"http": 502,
|
|
118
|
+
"retryable": true,
|
|
119
|
+
"meaning": "The provider failed or returned no image after the Gateway's retries"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"code": "upstream_timeout",
|
|
123
|
+
"http": 504,
|
|
124
|
+
"retryable": true,
|
|
125
|
+
"meaning": "The provider didn't return an image within 4 minutes"
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
"rules": [
|
|
129
|
+
{
|
|
130
|
+
"id": "images.aliases-only",
|
|
131
|
+
"kind": "must",
|
|
132
|
+
"text": "Ask for an image alias (image-default, image-pro or image-openai), never a provider model id.",
|
|
133
|
+
"enforced_by": "gateway",
|
|
134
|
+
"probe": "images.generate.image-default"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"id": "images.references-google-only",
|
|
138
|
+
"kind": "must_not",
|
|
139
|
+
"text": "Send reference images to an OpenAI alias. The Gateway refuses it with invalid_request rather than dropping them.",
|
|
140
|
+
"enforced_by": "gateway",
|
|
141
|
+
"probe": "images.generate.references-openai-refused"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"id": "images.no-silent-fallback",
|
|
145
|
+
"kind": "must_not",
|
|
146
|
+
"text": "Expect another provider to step in when one fails. The alias decides the provider; a failure is an error.",
|
|
147
|
+
"enforced_by": "gateway",
|
|
148
|
+
"probe": "images.generate.image-openai"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"id": "images.store-resolved-model",
|
|
152
|
+
"kind": "must",
|
|
153
|
+
"text": "Store the resolved model and request id with any image kept.",
|
|
154
|
+
"enforced_by": "review",
|
|
155
|
+
"probe": "images.generate.image-pro"
|
|
156
|
+
}
|
|
157
|
+
],
|
|
158
|
+
"examples": [
|
|
159
|
+
{
|
|
160
|
+
"id": "images.example.generate",
|
|
161
|
+
"title": "Generate one image",
|
|
162
|
+
"lang": "ts",
|
|
163
|
+
"probe": "images.generate.image-default",
|
|
164
|
+
"code": "const ai = createAi({ key: env.WORKBENCH_GATEWAY_KEY, env: workbenchEnv(env) })\nconst img = await ai.images.generate({ prompt, aspectRatio: '3:2', job: 'article.hero' })\nawait env.IMAGES.put(key, img.bytes, { httpMetadata: { contentType: img.mimeType }, customMetadata: { model: img.model, requestId: img.requestId } })"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"id": "images.example.reference",
|
|
168
|
+
"title": "Generate from a reference image (Google aliases only)",
|
|
169
|
+
"lang": "ts",
|
|
170
|
+
"probe": "images.generate.references-google",
|
|
171
|
+
"code": "const img = await ai.images.generate({ prompt: 'The same scene at dusk', aspectRatio: '1:1', job: 'article.hero', model: 'image-default', references: [{ bytes, mimeType: 'image/png' }] })"
|
|
172
|
+
}
|
|
173
|
+
],
|
|
174
|
+
"probes": [
|
|
175
|
+
{
|
|
176
|
+
"id": "images.generate.image-default",
|
|
177
|
+
"half": "runtime",
|
|
178
|
+
"expect": "pass",
|
|
179
|
+
"smoke": true,
|
|
180
|
+
"cost_hint_usd": 0.067
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"id": "images.generate.image-pro",
|
|
184
|
+
"half": "runtime",
|
|
185
|
+
"expect": "pass",
|
|
186
|
+
"cost_hint_usd": 0.134
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"id": "images.generate.image-openai",
|
|
190
|
+
"half": "runtime",
|
|
191
|
+
"expect": "pass",
|
|
192
|
+
"cost_hint_usd": 0.011
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"id": "images.generate.references-google",
|
|
196
|
+
"half": "runtime",
|
|
197
|
+
"expect": "pass",
|
|
198
|
+
"cost_hint_usd": 0.07
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"id": "images.generate.references-openai-refused",
|
|
202
|
+
"half": "runtime",
|
|
203
|
+
"expect": "refused_as_expected",
|
|
204
|
+
"refusal": "invalid_request",
|
|
205
|
+
"cost_hint_usd": 0
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Images
|
|
2
|
+
|
|
3
|
+
Apps generate images through the Gateway with `ai.images.generate` from `@mccaskie-labs/workbench-ai`, or `POST /v1/images/generate` directly. The Gateway holds the Google and OpenAI keys; the app holds only its Gateway key, which needs the `images` scope.
|
|
4
|
+
|
|
5
|
+
## Aliases
|
|
6
|
+
|
|
7
|
+
Ask for an alias, never a provider model id. The alias decides the provider, and the response names the model it resolved to.
|
|
8
|
+
|
|
9
|
+
| Alias | Provider | Model | List price per image |
|
|
10
|
+
| --- | --- | --- | --- |
|
|
11
|
+
| `image-default` | Google | `gemini-3.1-flash-image` (Nano Banana 2) | about US$0.067 at 1K |
|
|
12
|
+
| `image-pro` | Google | `gemini-3-pro-image` (Nano Banana Pro) | about US$0.134 at 1K |
|
|
13
|
+
| `image-openai` | OpenAI | `gpt-image-2.5-sunburst`, medium quality | US$0.011 |
|
|
14
|
+
|
|
15
|
+
Google images are charged from the token counts in the response when it has them, and at the list price when it doesn't. There is no automatic fallback: if `image-default` fails, the call fails, and the app decides whether to try another alias.
|
|
16
|
+
|
|
17
|
+
## Shape and size
|
|
18
|
+
|
|
19
|
+
One image per call. `aspect_ratio` is one of `1:1 3:2 2:3 4:3 3:4 16:9 9:16`. Google models make 1K images (3:2 is 1264×848). OpenAI makes one of its three sizes, the nearest to the ratio asked for (1024×1024, 1536×1024 or 1024×1536), so check the bytes' real size if the shape matters.
|
|
20
|
+
|
|
21
|
+
## References
|
|
22
|
+
|
|
23
|
+
Reference images (`references`, up to 20 MB of request in total) go to Google aliases only. Sending one to `image-openai` is refused with `invalid_request` rather than quietly dropped.
|
|
24
|
+
|
|
25
|
+
## Keep the provenance
|
|
26
|
+
|
|
27
|
+
Store the resolved model and the request id with any image you keep (R2 custom metadata works), so a usage row and a picture can be matched later.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const MANIFEST_VERSION: "
|
|
1
|
+
export declare const MANIFEST_VERSION: "ce5a8067dcd6";
|
|
2
2
|
export declare const capabilities: {
|
|
3
3
|
readonly ai: {
|
|
4
4
|
readonly $schema: "./schema.json";
|
|
@@ -398,6 +398,251 @@ export declare const capabilities: {
|
|
|
398
398
|
readonly note: "Manual: grep of the repo and cf_worker_secret_list show only WORKBENCH_GATEWAY_* secrets";
|
|
399
399
|
}];
|
|
400
400
|
};
|
|
401
|
+
readonly "image-search": {
|
|
402
|
+
readonly $schema: "./schema.json";
|
|
403
|
+
readonly format: 1;
|
|
404
|
+
readonly component: "image-search";
|
|
405
|
+
readonly title: "Image search through the Gateway";
|
|
406
|
+
readonly summary: "Web image search through Serper, normalised to one result shape and metered against the project's budget. Apps never hold a Serper key.";
|
|
407
|
+
readonly status: "planned";
|
|
408
|
+
readonly surfaces: {
|
|
409
|
+
readonly gateway: readonly ["POST /v1/images/search"];
|
|
410
|
+
readonly package: readonly ["Ai.images.search"];
|
|
411
|
+
};
|
|
412
|
+
readonly config: readonly [{
|
|
413
|
+
readonly name: "WORKBENCH_GATEWAY_KEY";
|
|
414
|
+
readonly required: true;
|
|
415
|
+
readonly secret: true;
|
|
416
|
+
readonly environments: readonly ["staging", "production"];
|
|
417
|
+
readonly description: "Project key with the search scope, delivered as a Worker secret by gw_key_issue";
|
|
418
|
+
}];
|
|
419
|
+
readonly allowed: {
|
|
420
|
+
readonly providers: readonly ["serper"];
|
|
421
|
+
readonly result_fields: readonly ["title", "image_url", "thumbnail_url", "width", "height", "source", "page_url"];
|
|
422
|
+
readonly required_headers: readonly ["X-Workbench-Job"];
|
|
423
|
+
};
|
|
424
|
+
readonly limits: readonly [{
|
|
425
|
+
readonly id: "image-search.num";
|
|
426
|
+
readonly value: "1-100";
|
|
427
|
+
readonly unit: "results";
|
|
428
|
+
readonly per: "request";
|
|
429
|
+
readonly enforced_by: "gateway";
|
|
430
|
+
readonly refusal: "invalid_request";
|
|
431
|
+
}, {
|
|
432
|
+
readonly id: "image-search.num-default";
|
|
433
|
+
readonly value: 10;
|
|
434
|
+
readonly unit: "results";
|
|
435
|
+
readonly per: "request";
|
|
436
|
+
readonly enforced_by: "gateway";
|
|
437
|
+
}, {
|
|
438
|
+
readonly id: "image-search.upstream-timeout";
|
|
439
|
+
readonly value: 90;
|
|
440
|
+
readonly unit: "seconds";
|
|
441
|
+
readonly per: "request";
|
|
442
|
+
readonly enforced_by: "gateway";
|
|
443
|
+
readonly refusal: "upstream_timeout";
|
|
444
|
+
}];
|
|
445
|
+
readonly errors: readonly [{
|
|
446
|
+
readonly code: "invalid_request";
|
|
447
|
+
readonly http: 400;
|
|
448
|
+
readonly retryable: false;
|
|
449
|
+
readonly meaning: "An empty query or a bad num, gl or hl";
|
|
450
|
+
}, {
|
|
451
|
+
readonly code: "budget_exceeded";
|
|
452
|
+
readonly http: 402;
|
|
453
|
+
readonly retryable: false;
|
|
454
|
+
readonly meaning: "Enforced monthly budget reached";
|
|
455
|
+
}, {
|
|
456
|
+
readonly code: "upstream_error";
|
|
457
|
+
readonly http: 502;
|
|
458
|
+
readonly retryable: true;
|
|
459
|
+
readonly meaning: "Serper failed after the Gateway's retries";
|
|
460
|
+
}, {
|
|
461
|
+
readonly code: "upstream_timeout";
|
|
462
|
+
readonly http: 504;
|
|
463
|
+
readonly retryable: true;
|
|
464
|
+
readonly meaning: "Serper didn't answer within 90 seconds";
|
|
465
|
+
}];
|
|
466
|
+
readonly rules: readonly [{
|
|
467
|
+
readonly id: "image-search.https-only";
|
|
468
|
+
readonly kind: "must";
|
|
469
|
+
readonly text: "Use only https image and page URLs from the results; the Gateway drops any result without one.";
|
|
470
|
+
readonly enforced_by: "gateway";
|
|
471
|
+
readonly probe: "image-search.serper";
|
|
472
|
+
}, {
|
|
473
|
+
readonly id: "image-search.licence-check";
|
|
474
|
+
readonly kind: "must";
|
|
475
|
+
readonly text: "Treat results as leads, not as images the app may republish. Check the source's licence before storing or showing one.";
|
|
476
|
+
readonly enforced_by: "review";
|
|
477
|
+
readonly probe: "image-search.serper";
|
|
478
|
+
}];
|
|
479
|
+
readonly examples: readonly [{
|
|
480
|
+
readonly id: "image-search.example.search";
|
|
481
|
+
readonly title: "Search for images";
|
|
482
|
+
readonly lang: "ts";
|
|
483
|
+
readonly code: "const ai = createAi({ key: env.WORKBENCH_GATEWAY_KEY, env: workbenchEnv(env) })\nconst results = await ai.images.search({ q: 'Mayon volcano', job: 'editor.image-search', num: 10 })";
|
|
484
|
+
readonly probe: "image-search.serper";
|
|
485
|
+
}];
|
|
486
|
+
readonly probes: readonly [{
|
|
487
|
+
readonly id: "image-search.serper";
|
|
488
|
+
readonly half: "runtime";
|
|
489
|
+
readonly expect: "pass";
|
|
490
|
+
readonly smoke: true;
|
|
491
|
+
readonly cost_hint_usd: 0.001;
|
|
492
|
+
}];
|
|
493
|
+
};
|
|
494
|
+
readonly images: {
|
|
495
|
+
readonly $schema: "./schema.json";
|
|
496
|
+
readonly format: 1;
|
|
497
|
+
readonly component: "images";
|
|
498
|
+
readonly title: "Image generation through the Gateway";
|
|
499
|
+
readonly summary: "One image per call from Gemini or OpenAI, chosen by a Workbench alias and metered against the project's budget. Apps never hold a Google or OpenAI key, and the Gateway never falls back to another provider.";
|
|
500
|
+
readonly status: "planned";
|
|
501
|
+
readonly surfaces: {
|
|
502
|
+
readonly gateway: readonly ["POST /v1/images/generate"];
|
|
503
|
+
readonly package: readonly ["Ai.images.generate"];
|
|
504
|
+
};
|
|
505
|
+
readonly config: readonly [{
|
|
506
|
+
readonly name: "WORKBENCH_GATEWAY_KEY";
|
|
507
|
+
readonly required: true;
|
|
508
|
+
readonly secret: true;
|
|
509
|
+
readonly environments: readonly ["staging", "production"];
|
|
510
|
+
readonly description: "Project key with the images scope, delivered as a Worker secret by gw_key_issue";
|
|
511
|
+
}];
|
|
512
|
+
readonly allowed: {
|
|
513
|
+
readonly aliases: readonly [{
|
|
514
|
+
readonly alias: "image-default";
|
|
515
|
+
readonly provider: "google";
|
|
516
|
+
readonly default_model: "gemini-3.1-flash-image";
|
|
517
|
+
}, {
|
|
518
|
+
readonly alias: "image-pro";
|
|
519
|
+
readonly provider: "google";
|
|
520
|
+
readonly default_model: "gemini-3-pro-image";
|
|
521
|
+
}, {
|
|
522
|
+
readonly alias: "image-openai";
|
|
523
|
+
readonly provider: "openai";
|
|
524
|
+
readonly default_model: "gpt-image-2.5-sunburst";
|
|
525
|
+
}];
|
|
526
|
+
readonly aspect_ratios: readonly ["1:1", "3:2", "2:3", "4:3", "3:4", "16:9", "9:16"];
|
|
527
|
+
readonly reference_providers: readonly ["google"];
|
|
528
|
+
readonly reference_mime_types: readonly ["image/png", "image/jpeg", "image/webp"];
|
|
529
|
+
readonly output_mime_types: readonly ["image/png", "image/jpeg"];
|
|
530
|
+
readonly required_headers: readonly ["X-Workbench-Job"];
|
|
531
|
+
};
|
|
532
|
+
readonly limits: readonly [{
|
|
533
|
+
readonly id: "images.per-call";
|
|
534
|
+
readonly value: 1;
|
|
535
|
+
readonly unit: "images";
|
|
536
|
+
readonly per: "request";
|
|
537
|
+
readonly enforced_by: "gateway";
|
|
538
|
+
}, {
|
|
539
|
+
readonly id: "images.request-size";
|
|
540
|
+
readonly value: 20971520;
|
|
541
|
+
readonly unit: "bytes";
|
|
542
|
+
readonly per: "request";
|
|
543
|
+
readonly enforced_by: "gateway";
|
|
544
|
+
readonly refusal: "invalid_request";
|
|
545
|
+
}, {
|
|
546
|
+
readonly id: "images.upstream-timeout";
|
|
547
|
+
readonly value: 240;
|
|
548
|
+
readonly unit: "seconds";
|
|
549
|
+
readonly per: "request";
|
|
550
|
+
readonly enforced_by: "gateway";
|
|
551
|
+
readonly refusal: "upstream_timeout";
|
|
552
|
+
}];
|
|
553
|
+
readonly errors: readonly [{
|
|
554
|
+
readonly code: "model_not_allowed";
|
|
555
|
+
readonly http: 400;
|
|
556
|
+
readonly retryable: false;
|
|
557
|
+
readonly meaning: "Not an image alias";
|
|
558
|
+
}, {
|
|
559
|
+
readonly code: "invalid_request";
|
|
560
|
+
readonly http: 400;
|
|
561
|
+
readonly retryable: false;
|
|
562
|
+
readonly meaning: "A bad aspect ratio, prompt or reference, or a reference on an OpenAI model";
|
|
563
|
+
}, {
|
|
564
|
+
readonly code: "budget_exceeded";
|
|
565
|
+
readonly http: 402;
|
|
566
|
+
readonly retryable: false;
|
|
567
|
+
readonly meaning: "Enforced monthly budget reached";
|
|
568
|
+
}, {
|
|
569
|
+
readonly code: "upstream_error";
|
|
570
|
+
readonly http: 502;
|
|
571
|
+
readonly retryable: true;
|
|
572
|
+
readonly meaning: "The provider failed or returned no image after the Gateway's retries";
|
|
573
|
+
}, {
|
|
574
|
+
readonly code: "upstream_timeout";
|
|
575
|
+
readonly http: 504;
|
|
576
|
+
readonly retryable: true;
|
|
577
|
+
readonly meaning: "The provider didn't return an image within 4 minutes";
|
|
578
|
+
}];
|
|
579
|
+
readonly rules: readonly [{
|
|
580
|
+
readonly id: "images.aliases-only";
|
|
581
|
+
readonly kind: "must";
|
|
582
|
+
readonly text: "Ask for an image alias (image-default, image-pro or image-openai), never a provider model id.";
|
|
583
|
+
readonly enforced_by: "gateway";
|
|
584
|
+
readonly probe: "images.generate.image-default";
|
|
585
|
+
}, {
|
|
586
|
+
readonly id: "images.references-google-only";
|
|
587
|
+
readonly kind: "must_not";
|
|
588
|
+
readonly text: "Send reference images to an OpenAI alias. The Gateway refuses it with invalid_request rather than dropping them.";
|
|
589
|
+
readonly enforced_by: "gateway";
|
|
590
|
+
readonly probe: "images.generate.references-openai-refused";
|
|
591
|
+
}, {
|
|
592
|
+
readonly id: "images.no-silent-fallback";
|
|
593
|
+
readonly kind: "must_not";
|
|
594
|
+
readonly text: "Expect another provider to step in when one fails. The alias decides the provider; a failure is an error.";
|
|
595
|
+
readonly enforced_by: "gateway";
|
|
596
|
+
readonly probe: "images.generate.image-openai";
|
|
597
|
+
}, {
|
|
598
|
+
readonly id: "images.store-resolved-model";
|
|
599
|
+
readonly kind: "must";
|
|
600
|
+
readonly text: "Store the resolved model and request id with any image kept.";
|
|
601
|
+
readonly enforced_by: "review";
|
|
602
|
+
readonly probe: "images.generate.image-pro";
|
|
603
|
+
}];
|
|
604
|
+
readonly examples: readonly [{
|
|
605
|
+
readonly id: "images.example.generate";
|
|
606
|
+
readonly title: "Generate one image";
|
|
607
|
+
readonly lang: "ts";
|
|
608
|
+
readonly code: "const ai = createAi({ key: env.WORKBENCH_GATEWAY_KEY, env: workbenchEnv(env) })\nconst img = await ai.images.generate({ prompt, aspectRatio: '3:2', job: 'article.hero' })\nawait env.IMAGES.put(key, img.bytes, { httpMetadata: { contentType: img.mimeType }, customMetadata: { model: img.model, requestId: img.requestId } })";
|
|
609
|
+
readonly probe: "images.generate.image-default";
|
|
610
|
+
}, {
|
|
611
|
+
readonly id: "images.example.reference";
|
|
612
|
+
readonly title: "Generate from a reference image (Google aliases only)";
|
|
613
|
+
readonly lang: "ts";
|
|
614
|
+
readonly code: "const img = await ai.images.generate({ prompt: 'The same scene at dusk', aspectRatio: '1:1', job: 'article.hero', model: 'image-default', references: [{ bytes, mimeType: 'image/png' }] })";
|
|
615
|
+
readonly probe: "images.generate.references-google";
|
|
616
|
+
}];
|
|
617
|
+
readonly probes: readonly [{
|
|
618
|
+
readonly id: "images.generate.image-default";
|
|
619
|
+
readonly half: "runtime";
|
|
620
|
+
readonly expect: "pass";
|
|
621
|
+
readonly smoke: true;
|
|
622
|
+
readonly cost_hint_usd: 0.067;
|
|
623
|
+
}, {
|
|
624
|
+
readonly id: "images.generate.image-pro";
|
|
625
|
+
readonly half: "runtime";
|
|
626
|
+
readonly expect: "pass";
|
|
627
|
+
readonly cost_hint_usd: 0.134;
|
|
628
|
+
}, {
|
|
629
|
+
readonly id: "images.generate.image-openai";
|
|
630
|
+
readonly half: "runtime";
|
|
631
|
+
readonly expect: "pass";
|
|
632
|
+
readonly cost_hint_usd: 0.011;
|
|
633
|
+
}, {
|
|
634
|
+
readonly id: "images.generate.references-google";
|
|
635
|
+
readonly half: "runtime";
|
|
636
|
+
readonly expect: "pass";
|
|
637
|
+
readonly cost_hint_usd: 0.07;
|
|
638
|
+
}, {
|
|
639
|
+
readonly id: "images.generate.references-openai-refused";
|
|
640
|
+
readonly half: "runtime";
|
|
641
|
+
readonly expect: "refused_as_expected";
|
|
642
|
+
readonly refusal: "invalid_request";
|
|
643
|
+
readonly cost_hint_usd: 0;
|
|
644
|
+
}];
|
|
645
|
+
};
|
|
401
646
|
};
|
|
402
647
|
export type Alias = (typeof capabilities)["ai"]["allowed"]["aliases"][number]["alias"];
|
|
403
648
|
export type GatewayScope = (typeof capabilities)["gateway"]["allowed"]["scopes"][number];
|
package/dist/capabilities.gen.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by `npm run capabilities:build` from capabilities/*.json. Do not edit.
|
|
2
2
|
// check:capabilities fails in Checks when this file is stale.
|
|
3
|
-
export const MANIFEST_VERSION = "
|
|
3
|
+
export const MANIFEST_VERSION = "ce5a8067dcd6";
|
|
4
4
|
export const capabilities = {
|
|
5
5
|
"ai": {
|
|
6
6
|
"$schema": "./schema.json",
|
|
@@ -550,5 +550,343 @@ export const capabilities = {
|
|
|
550
550
|
"note": "Manual: grep of the repo and cf_worker_secret_list show only WORKBENCH_GATEWAY_* secrets"
|
|
551
551
|
}
|
|
552
552
|
]
|
|
553
|
+
},
|
|
554
|
+
"image-search": {
|
|
555
|
+
"$schema": "./schema.json",
|
|
556
|
+
"format": 1,
|
|
557
|
+
"component": "image-search",
|
|
558
|
+
"title": "Image search through the Gateway",
|
|
559
|
+
"summary": "Web image search through Serper, normalised to one result shape and metered against the project's budget. Apps never hold a Serper key.",
|
|
560
|
+
"status": "planned",
|
|
561
|
+
"surfaces": {
|
|
562
|
+
"gateway": [
|
|
563
|
+
"POST /v1/images/search"
|
|
564
|
+
],
|
|
565
|
+
"package": [
|
|
566
|
+
"Ai.images.search"
|
|
567
|
+
]
|
|
568
|
+
},
|
|
569
|
+
"config": [
|
|
570
|
+
{
|
|
571
|
+
"name": "WORKBENCH_GATEWAY_KEY",
|
|
572
|
+
"required": true,
|
|
573
|
+
"secret": true,
|
|
574
|
+
"environments": [
|
|
575
|
+
"staging",
|
|
576
|
+
"production"
|
|
577
|
+
],
|
|
578
|
+
"description": "Project key with the search scope, delivered as a Worker secret by gw_key_issue"
|
|
579
|
+
}
|
|
580
|
+
],
|
|
581
|
+
"allowed": {
|
|
582
|
+
"providers": [
|
|
583
|
+
"serper"
|
|
584
|
+
],
|
|
585
|
+
"result_fields": [
|
|
586
|
+
"title",
|
|
587
|
+
"image_url",
|
|
588
|
+
"thumbnail_url",
|
|
589
|
+
"width",
|
|
590
|
+
"height",
|
|
591
|
+
"source",
|
|
592
|
+
"page_url"
|
|
593
|
+
],
|
|
594
|
+
"required_headers": [
|
|
595
|
+
"X-Workbench-Job"
|
|
596
|
+
]
|
|
597
|
+
},
|
|
598
|
+
"limits": [
|
|
599
|
+
{
|
|
600
|
+
"id": "image-search.num",
|
|
601
|
+
"value": "1-100",
|
|
602
|
+
"unit": "results",
|
|
603
|
+
"per": "request",
|
|
604
|
+
"enforced_by": "gateway",
|
|
605
|
+
"refusal": "invalid_request"
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
"id": "image-search.num-default",
|
|
609
|
+
"value": 10,
|
|
610
|
+
"unit": "results",
|
|
611
|
+
"per": "request",
|
|
612
|
+
"enforced_by": "gateway"
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
"id": "image-search.upstream-timeout",
|
|
616
|
+
"value": 90,
|
|
617
|
+
"unit": "seconds",
|
|
618
|
+
"per": "request",
|
|
619
|
+
"enforced_by": "gateway",
|
|
620
|
+
"refusal": "upstream_timeout"
|
|
621
|
+
}
|
|
622
|
+
],
|
|
623
|
+
"errors": [
|
|
624
|
+
{
|
|
625
|
+
"code": "invalid_request",
|
|
626
|
+
"http": 400,
|
|
627
|
+
"retryable": false,
|
|
628
|
+
"meaning": "An empty query or a bad num, gl or hl"
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
"code": "budget_exceeded",
|
|
632
|
+
"http": 402,
|
|
633
|
+
"retryable": false,
|
|
634
|
+
"meaning": "Enforced monthly budget reached"
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
"code": "upstream_error",
|
|
638
|
+
"http": 502,
|
|
639
|
+
"retryable": true,
|
|
640
|
+
"meaning": "Serper failed after the Gateway's retries"
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
"code": "upstream_timeout",
|
|
644
|
+
"http": 504,
|
|
645
|
+
"retryable": true,
|
|
646
|
+
"meaning": "Serper didn't answer within 90 seconds"
|
|
647
|
+
}
|
|
648
|
+
],
|
|
649
|
+
"rules": [
|
|
650
|
+
{
|
|
651
|
+
"id": "image-search.https-only",
|
|
652
|
+
"kind": "must",
|
|
653
|
+
"text": "Use only https image and page URLs from the results; the Gateway drops any result without one.",
|
|
654
|
+
"enforced_by": "gateway",
|
|
655
|
+
"probe": "image-search.serper"
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
"id": "image-search.licence-check",
|
|
659
|
+
"kind": "must",
|
|
660
|
+
"text": "Treat results as leads, not as images the app may republish. Check the source's licence before storing or showing one.",
|
|
661
|
+
"enforced_by": "review",
|
|
662
|
+
"probe": "image-search.serper"
|
|
663
|
+
}
|
|
664
|
+
],
|
|
665
|
+
"examples": [
|
|
666
|
+
{
|
|
667
|
+
"id": "image-search.example.search",
|
|
668
|
+
"title": "Search for images",
|
|
669
|
+
"lang": "ts",
|
|
670
|
+
"code": "const ai = createAi({ key: env.WORKBENCH_GATEWAY_KEY, env: workbenchEnv(env) })\nconst results = await ai.images.search({ q: 'Mayon volcano', job: 'editor.image-search', num: 10 })",
|
|
671
|
+
"probe": "image-search.serper"
|
|
672
|
+
}
|
|
673
|
+
],
|
|
674
|
+
"probes": [
|
|
675
|
+
{
|
|
676
|
+
"id": "image-search.serper",
|
|
677
|
+
"half": "runtime",
|
|
678
|
+
"expect": "pass",
|
|
679
|
+
"smoke": true,
|
|
680
|
+
"cost_hint_usd": 0.001
|
|
681
|
+
}
|
|
682
|
+
]
|
|
683
|
+
},
|
|
684
|
+
"images": {
|
|
685
|
+
"$schema": "./schema.json",
|
|
686
|
+
"format": 1,
|
|
687
|
+
"component": "images",
|
|
688
|
+
"title": "Image generation through the Gateway",
|
|
689
|
+
"summary": "One image per call from Gemini or OpenAI, chosen by a Workbench alias and metered against the project's budget. Apps never hold a Google or OpenAI key, and the Gateway never falls back to another provider.",
|
|
690
|
+
"status": "planned",
|
|
691
|
+
"surfaces": {
|
|
692
|
+
"gateway": [
|
|
693
|
+
"POST /v1/images/generate"
|
|
694
|
+
],
|
|
695
|
+
"package": [
|
|
696
|
+
"Ai.images.generate"
|
|
697
|
+
]
|
|
698
|
+
},
|
|
699
|
+
"config": [
|
|
700
|
+
{
|
|
701
|
+
"name": "WORKBENCH_GATEWAY_KEY",
|
|
702
|
+
"required": true,
|
|
703
|
+
"secret": true,
|
|
704
|
+
"environments": [
|
|
705
|
+
"staging",
|
|
706
|
+
"production"
|
|
707
|
+
],
|
|
708
|
+
"description": "Project key with the images scope, delivered as a Worker secret by gw_key_issue"
|
|
709
|
+
}
|
|
710
|
+
],
|
|
711
|
+
"allowed": {
|
|
712
|
+
"aliases": [
|
|
713
|
+
{
|
|
714
|
+
"alias": "image-default",
|
|
715
|
+
"provider": "google",
|
|
716
|
+
"default_model": "gemini-3.1-flash-image"
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
"alias": "image-pro",
|
|
720
|
+
"provider": "google",
|
|
721
|
+
"default_model": "gemini-3-pro-image"
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
"alias": "image-openai",
|
|
725
|
+
"provider": "openai",
|
|
726
|
+
"default_model": "gpt-image-2.5-sunburst"
|
|
727
|
+
}
|
|
728
|
+
],
|
|
729
|
+
"aspect_ratios": [
|
|
730
|
+
"1:1",
|
|
731
|
+
"3:2",
|
|
732
|
+
"2:3",
|
|
733
|
+
"4:3",
|
|
734
|
+
"3:4",
|
|
735
|
+
"16:9",
|
|
736
|
+
"9:16"
|
|
737
|
+
],
|
|
738
|
+
"reference_providers": [
|
|
739
|
+
"google"
|
|
740
|
+
],
|
|
741
|
+
"reference_mime_types": [
|
|
742
|
+
"image/png",
|
|
743
|
+
"image/jpeg",
|
|
744
|
+
"image/webp"
|
|
745
|
+
],
|
|
746
|
+
"output_mime_types": [
|
|
747
|
+
"image/png",
|
|
748
|
+
"image/jpeg"
|
|
749
|
+
],
|
|
750
|
+
"required_headers": [
|
|
751
|
+
"X-Workbench-Job"
|
|
752
|
+
]
|
|
753
|
+
},
|
|
754
|
+
"limits": [
|
|
755
|
+
{
|
|
756
|
+
"id": "images.per-call",
|
|
757
|
+
"value": 1,
|
|
758
|
+
"unit": "images",
|
|
759
|
+
"per": "request",
|
|
760
|
+
"enforced_by": "gateway"
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
"id": "images.request-size",
|
|
764
|
+
"value": 20971520,
|
|
765
|
+
"unit": "bytes",
|
|
766
|
+
"per": "request",
|
|
767
|
+
"enforced_by": "gateway",
|
|
768
|
+
"refusal": "invalid_request"
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
"id": "images.upstream-timeout",
|
|
772
|
+
"value": 240,
|
|
773
|
+
"unit": "seconds",
|
|
774
|
+
"per": "request",
|
|
775
|
+
"enforced_by": "gateway",
|
|
776
|
+
"refusal": "upstream_timeout"
|
|
777
|
+
}
|
|
778
|
+
],
|
|
779
|
+
"errors": [
|
|
780
|
+
{
|
|
781
|
+
"code": "model_not_allowed",
|
|
782
|
+
"http": 400,
|
|
783
|
+
"retryable": false,
|
|
784
|
+
"meaning": "Not an image alias"
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
"code": "invalid_request",
|
|
788
|
+
"http": 400,
|
|
789
|
+
"retryable": false,
|
|
790
|
+
"meaning": "A bad aspect ratio, prompt or reference, or a reference on an OpenAI model"
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
"code": "budget_exceeded",
|
|
794
|
+
"http": 402,
|
|
795
|
+
"retryable": false,
|
|
796
|
+
"meaning": "Enforced monthly budget reached"
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
"code": "upstream_error",
|
|
800
|
+
"http": 502,
|
|
801
|
+
"retryable": true,
|
|
802
|
+
"meaning": "The provider failed or returned no image after the Gateway's retries"
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
"code": "upstream_timeout",
|
|
806
|
+
"http": 504,
|
|
807
|
+
"retryable": true,
|
|
808
|
+
"meaning": "The provider didn't return an image within 4 minutes"
|
|
809
|
+
}
|
|
810
|
+
],
|
|
811
|
+
"rules": [
|
|
812
|
+
{
|
|
813
|
+
"id": "images.aliases-only",
|
|
814
|
+
"kind": "must",
|
|
815
|
+
"text": "Ask for an image alias (image-default, image-pro or image-openai), never a provider model id.",
|
|
816
|
+
"enforced_by": "gateway",
|
|
817
|
+
"probe": "images.generate.image-default"
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
"id": "images.references-google-only",
|
|
821
|
+
"kind": "must_not",
|
|
822
|
+
"text": "Send reference images to an OpenAI alias. The Gateway refuses it with invalid_request rather than dropping them.",
|
|
823
|
+
"enforced_by": "gateway",
|
|
824
|
+
"probe": "images.generate.references-openai-refused"
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
"id": "images.no-silent-fallback",
|
|
828
|
+
"kind": "must_not",
|
|
829
|
+
"text": "Expect another provider to step in when one fails. The alias decides the provider; a failure is an error.",
|
|
830
|
+
"enforced_by": "gateway",
|
|
831
|
+
"probe": "images.generate.image-openai"
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
"id": "images.store-resolved-model",
|
|
835
|
+
"kind": "must",
|
|
836
|
+
"text": "Store the resolved model and request id with any image kept.",
|
|
837
|
+
"enforced_by": "review",
|
|
838
|
+
"probe": "images.generate.image-pro"
|
|
839
|
+
}
|
|
840
|
+
],
|
|
841
|
+
"examples": [
|
|
842
|
+
{
|
|
843
|
+
"id": "images.example.generate",
|
|
844
|
+
"title": "Generate one image",
|
|
845
|
+
"lang": "ts",
|
|
846
|
+
"code": "const ai = createAi({ key: env.WORKBENCH_GATEWAY_KEY, env: workbenchEnv(env) })\nconst img = await ai.images.generate({ prompt, aspectRatio: '3:2', job: 'article.hero' })\nawait env.IMAGES.put(key, img.bytes, { httpMetadata: { contentType: img.mimeType }, customMetadata: { model: img.model, requestId: img.requestId } })",
|
|
847
|
+
"probe": "images.generate.image-default"
|
|
848
|
+
},
|
|
849
|
+
{
|
|
850
|
+
"id": "images.example.reference",
|
|
851
|
+
"title": "Generate from a reference image (Google aliases only)",
|
|
852
|
+
"lang": "ts",
|
|
853
|
+
"code": "const img = await ai.images.generate({ prompt: 'The same scene at dusk', aspectRatio: '1:1', job: 'article.hero', model: 'image-default', references: [{ bytes, mimeType: 'image/png' }] })",
|
|
854
|
+
"probe": "images.generate.references-google"
|
|
855
|
+
}
|
|
856
|
+
],
|
|
857
|
+
"probes": [
|
|
858
|
+
{
|
|
859
|
+
"id": "images.generate.image-default",
|
|
860
|
+
"half": "runtime",
|
|
861
|
+
"expect": "pass",
|
|
862
|
+
"smoke": true,
|
|
863
|
+
"cost_hint_usd": 0.067
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
"id": "images.generate.image-pro",
|
|
867
|
+
"half": "runtime",
|
|
868
|
+
"expect": "pass",
|
|
869
|
+
"cost_hint_usd": 0.134
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
"id": "images.generate.image-openai",
|
|
873
|
+
"half": "runtime",
|
|
874
|
+
"expect": "pass",
|
|
875
|
+
"cost_hint_usd": 0.011
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
"id": "images.generate.references-google",
|
|
879
|
+
"half": "runtime",
|
|
880
|
+
"expect": "pass",
|
|
881
|
+
"cost_hint_usd": 0.07
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
"id": "images.generate.references-openai-refused",
|
|
885
|
+
"half": "runtime",
|
|
886
|
+
"expect": "refused_as_expected",
|
|
887
|
+
"refusal": "invalid_request",
|
|
888
|
+
"cost_hint_usd": 0
|
|
889
|
+
}
|
|
890
|
+
]
|
|
553
891
|
}
|
|
554
892
|
};
|
|
@@ -40,6 +40,31 @@ export declare const allowedSchemas: {
|
|
|
40
40
|
server_tools: z.ZodArray<z.ZodString>;
|
|
41
41
|
required_headers: z.ZodArray<z.ZodString>;
|
|
42
42
|
}, z.core.$strict>;
|
|
43
|
+
images: z.ZodObject<{
|
|
44
|
+
aliases: z.ZodArray<z.ZodObject<{
|
|
45
|
+
alias: z.ZodString;
|
|
46
|
+
default_model: z.ZodString;
|
|
47
|
+
provider: z.ZodEnum<{
|
|
48
|
+
google: "google";
|
|
49
|
+
openai: "openai";
|
|
50
|
+
}>;
|
|
51
|
+
}, z.core.$strict>>;
|
|
52
|
+
aspect_ratios: z.ZodArray<z.ZodString>;
|
|
53
|
+
reference_providers: z.ZodArray<z.ZodEnum<{
|
|
54
|
+
google: "google";
|
|
55
|
+
openai: "openai";
|
|
56
|
+
}>>;
|
|
57
|
+
reference_mime_types: z.ZodArray<z.ZodString>;
|
|
58
|
+
output_mime_types: z.ZodArray<z.ZodString>;
|
|
59
|
+
required_headers: z.ZodArray<z.ZodString>;
|
|
60
|
+
}, z.core.$strict>;
|
|
61
|
+
"image-search": z.ZodObject<{
|
|
62
|
+
providers: z.ZodArray<z.ZodEnum<{
|
|
63
|
+
serper: "serper";
|
|
64
|
+
}>>;
|
|
65
|
+
result_fields: z.ZodArray<z.ZodString>;
|
|
66
|
+
required_headers: z.ZodArray<z.ZodString>;
|
|
67
|
+
}, z.core.$strict>;
|
|
43
68
|
config: z.ZodObject<{
|
|
44
69
|
environments: z.ZodArray<z.ZodEnum<{
|
|
45
70
|
development: "development";
|
|
@@ -100,6 +100,19 @@ export const allowedSchemas = {
|
|
|
100
100
|
server_tools: z.array(z.string().min(1)),
|
|
101
101
|
required_headers: z.array(z.string().min(1)),
|
|
102
102
|
}),
|
|
103
|
+
images: z.strictObject({
|
|
104
|
+
aliases: z.array(aliasEntry.extend({ provider: z.enum(["google", "openai"]) })).min(1),
|
|
105
|
+
aspect_ratios: z.array(z.string().regex(/^[1-9][0-9]?:[1-9][0-9]?$/, "w:h")).min(1),
|
|
106
|
+
reference_providers: z.array(z.enum(["google", "openai"])),
|
|
107
|
+
reference_mime_types: z.array(z.string().regex(/^image\/[a-z0-9.+-]+$/)).min(1),
|
|
108
|
+
output_mime_types: z.array(z.string().regex(/^image\/[a-z0-9.+-]+$/)).min(1),
|
|
109
|
+
required_headers: z.array(z.string().min(1)),
|
|
110
|
+
}),
|
|
111
|
+
"image-search": z.strictObject({
|
|
112
|
+
providers: z.array(z.enum(["serper"])).min(1),
|
|
113
|
+
result_fields: z.array(z.string().regex(/^[a-z][a-z0-9_]*$/)).min(1),
|
|
114
|
+
required_headers: z.array(z.string().min(1)),
|
|
115
|
+
}),
|
|
103
116
|
config: z.strictObject({
|
|
104
117
|
environments: z.array(envName).min(1),
|
|
105
118
|
}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mccaskie-labs/workbench-config",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Workbench app configuration: WORKBENCH_ENV, typed variables that fail by name, secrets that are never printed, and the capabilities manifest.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|