@crvouga/mockingbird-service-bedrock 3.7.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,17 @@
1
1
  # Changelog — @crvouga/mockingbird-service-bedrock
2
2
 
3
- ## 3.7.0 (2026-10-19)
3
+ ## 3.8.0 (2026-10-21)
4
+
5
+ ### Features
6
+
7
+ - raise the serve and fleet body limit to the S3 maximum ([86b9e2b](https://github.com/crvouga/mockingbird/commit/86b9e2b80bcaaaef57f52edbac50b27da0b42f33))
8
+
9
+ ### Fixes and improvements
10
+
11
+ - finish junction, notion and bedrock docs and tests ([f1b4ccb](https://github.com/crvouga/mockingbird/commit/f1b4ccb4441144b5151b536aaacc8519b2e86075))
12
+ - reject unknown script match keys and add schema, document and cache validation ([af0c60d](https://github.com/crvouga/mockingbird/commit/af0c60d672ec6810fca1b9a69559f34181303499))
13
+
14
+ ## 3.7.0 (2026-10-18)
4
15
 
5
16
  ### Features
6
17
 
package/README.md CHANGED
@@ -106,7 +106,7 @@ HTTP/1.1 only — fine for the AI SDK, not for the SDK v3 Bedrock client.)
106
106
  | --- | --- |
107
107
  | `POST /model/{modelId}/converse` | Converse JSON: `output.message.content[]` (`text`, `toolUse`, `reasoningContent`), `stopReason`, `usage` (with `cacheRead/WriteInputTokens` when a `cachePoint` is present), `metrics`, `trace.guardrail` (with `guardrailConfig.trace: "enabled"`), `x-amzn-RequestId`. |
108
108
  | `POST /model/{modelId}/converse-stream` | The same answer as event frames: `messageStart`, `contentBlockStart` (tool use), `contentBlockDelta` (`text`, `toolUse.input` partial JSON, `reasoningContent`), `contentBlockStop`, `messageStop`, `metadata`; exception frames mid-stream. |
109
- | `POST /model/{modelId}/invoke` | `amazon.titan-embed-text-*`: `{embedding, inputTextTokenCount}` — a deterministic unit vector from SHA-256(`inputText`) (1024-d by default; `dimensions` 256/512/1024). Claude models: an Anthropic Messages body in, a Messages response out. |
109
+ | `POST /model/{modelId}/invoke` | `amazon.titan-embed-text-*`: `{embedding, inputTextTokenCount}` — a deterministic unit vector from SHA-256(`inputText`) (1024-d by default; `dimensions` 256/512/1024), or a lexical one with `embeddingMode: "lexical"` (see Settings). Claude models: an Anthropic Messages body in, a Messages response out. |
110
110
  | `POST /model/{modelId}/invoke-with-bidirectional-stream` | Nova Sonic (`*sonic*`), HTTP/2 duplex: reads `chunk` events as they arrive (SigV4 envelopes unwrapped), answers each user turn with `textOutput` + 24 kHz PCM tone `audioOutput` + `toolUse`, then `usageEvent`; `completionEnd` after `sessionEnd`. |
111
111
  | `POST /harnesses/invoke?harnessArn=` | AgentCore harness stream: `messageStart`, `contentBlockDelta` (`text` or `toolResult[]`), `contentBlockStop`, `messageStop`, `metadata`; `validationException` / `internalServerException` / `runtimeClientError` frames. |
112
112
 
@@ -114,7 +114,8 @@ HTTP/1.1 only — fine for the AI SDK, not for the SDK v3 Bedrock client.)
114
114
  accepted without verification. Request checks Bedrock makes and our code branches on are
115
115
  enforced: role alternation, first/last message is the user (assistant **prefill** is rejected
116
116
  for Claude 4.5+), tool-use/tool-result pairing, `toolConfig` required with tool blocks, a
117
- document needs a sibling text block, `temperature` + `top_p` together on Claude 4.5+. Errors are
117
+ document needs a sibling text block, `temperature` + `top_p` together on Claude 4.5+, plus the
118
+ best-guess checks listed under "Best guesses, not oracle-verified". Errors are
118
119
  `x-amzn-ErrorType: <Name>:http://internal.amazon.com/coral/com.amazon.bedrock/` + `{"message"}`.
119
120
  Output longer than `maxTokens` (≈4 chars/token) is cut with `stopReason: "max_tokens"`.
120
121
 
@@ -127,8 +128,16 @@ how many calls it answers.
127
128
  - **Match keys:** `modelId` (glob), `operation` (`Converse`, `ConverseStream`, `InvokeModel`,
128
129
  `InvokeModelWithBidirectionalStream`, `InvokeHarness`), `lastUserText` (string = contains, or
129
130
  `{contains, regex, flags}`), `systemHash` (SHA-256 hex of the system text blocks joined with
130
- `\n`), `toolsInclude`, `toolChoice` (`auto` / `any` / a tool name), `hasDocument`, `hasImage`,
131
- `callIndex` (0-based index of the call in the namespace).
131
+ `\n`), `systemText` (the same joined system text, `cachePoint` blocks skipped; string =
132
+ case-insensitive contains, or `{contains, regex, flags}`; a call with no system prompt never
133
+ matches; for `InvokeModel` the Claude `system` string or text blocks, never stored),
134
+ `jsonSchemaName` (`outputConfig.textFormat.structure.jsonSchema.name`; string =
135
+ case-insensitive equality, or `{contains, regex, flags}`; a call without a named `outputConfig`
136
+ schema never matches), `toolsInclude`, `toolChoice` (`auto` / `any` / a tool name), `hasDocument`, `hasImage`,
137
+ `callIndex` (0-based index of the call in the namespace). `match` is a closed set: an unknown
138
+ or misspelled key (`lastUserTxt`) or a malformed criterion makes `PUT`/`POST /__admin/scripts`
139
+ answer 400 (`scripts[0].match.lastUserTxt: unknown match field (one of ...)`) instead of
140
+ silently matching every call; the same goes for an unknown key inside a text match.
132
141
  - **Turn selection** reads the conversation, not server state: turn *n* answers the call that
133
142
  comes after *n* assistant messages since the member last said something. So turn 0 is the
134
143
  first call of a user turn, turn 1 is the call that resumes after a tool result, and every new
@@ -166,6 +175,7 @@ applies to every model and harness call in the calling namespace):
166
175
  | `latency` (`latencyMs`) | the response starts after 2 s on the emulator clock |
167
176
  | `truncated_frame` | the stream stops half-way through a frame (both decoders throw) |
168
177
  | `model_timeout`, `service_unavailable`, `access_denied`, `internal_server` | 408 / 503 / 403 / 500 with the matching `x-amzn-ErrorType` |
178
+ | `resource_not_found` | 404 `ResourceNotFoundException` ("The specified resource ARN was not found. Check the ARN and try again.", per the Converse API reference), before any frame, on Converse, ConverseStream and InvokeModel |
169
179
 
170
180
  Chunk pacing and latency wait on the **emulator clock**: freeze it (`POST /__admin/clock
171
181
  {"freeze": true}`) and advance it to release each chunk, so time-to-first-token tests are exact.
@@ -179,12 +189,53 @@ Chunk pacing and latency wait on the **emulator clock**: freeze it (`POST /__adm
179
189
  | `GET /__admin/scripts` | Scripts plus `stats` (`calls`, `scripted`, `unscripted`, `byScript`, `byFallback`, `byOperation`). |
180
190
  | `DELETE /__admin/scripts[?id=]` | Remove one or all. |
181
191
  | `GET /__admin/model-metrics` | Just the stats. |
182
- | `GET/PUT /__admin/settings` | `defaultText`, `chunkSize` (16), `delayMsPerChunk` (0), `audioTurnChunks` (0: a Nova Sonic spoken turn ends at the audio `contentEnd`; n: after n audio frames). |
192
+ | `GET/PUT /__admin/settings` | `defaultText`, `chunkSize` (16), `delayMsPerChunk` (0), `audioTurnChunks` (0: a Nova Sonic spoken turn ends at the audio `contentEnd`; n: after n audio frames), `embeddingMode` (`sha256` default, or `lexical`), `validatePromptCaching` (`true` default; `false` disables the model check on `cachePoint`). |
193
+
194
+ `embeddingMode: "lexical"` (opt-in; the test-control request in #522, not a Bedrock behaviour)
195
+ keeps Titan vectors deterministic and unit length but makes them similarity-preserving: each
196
+ word (lowercased, accents and punctuation dropped) adds to a SHA-256-chosen signed dimension, so
197
+ texts differing only in case, punctuation or spacing have cosine similarity 1, texts sharing most
198
+ words score high, and texts with no word in common score near 0. It is bag-of-words, not a
199
+ language model: synonyms ("evening" / "night") stay apart. Scripted per-text vectors are not
200
+ implemented. `normalize: false` is ignored in both modes (vectors are always unit length).
183
201
 
184
202
  The request journal (`GET /__admin/requests`) records per call only `modelId`, `script` (or
185
203
  `unscripted:<default>`), tool names, flags (`cachePoint`, `guardrail`, `document`, `image`,
186
204
  `structured:<form>`), `stopReason` and token counts — never prompt or message text.
187
205
 
206
+ ### Best guesses, not oracle-verified
207
+
208
+ These request checks come from the AWS documentation and what consumers reported about live
209
+ Bedrock, not from observing the service; the exact message text of the ones marked "ours" is an
210
+ emulator choice. All answer `400 ValidationException`.
211
+
212
+ - **Structured-output schemas** (`outputConfig.textFormat.structure.jsonSchema.schema`, Converse
213
+ and ConverseStream, [docs](https://docs.aws.amazon.com/bedrock/latest/userguide/structured-output.html)):
214
+ `schema` that is not a JSON object (ours); `oneOf` (`Schema type 'oneOf' is not supported`);
215
+ `additionalProperties` set to anything other than `false` on any node, `true` or a sub-schema
216
+ (`For 'object' type, 'additionalProperties: <X>' is not supported. Please set
217
+ 'additionalProperties' to false`); a string-only `enum` combined with a union `type` such as
218
+ `["string","null"]` (ours); `minimum`, `maximum`, `multipleOf`, `minLength`, `maxLength`,
219
+ `minItems` above 1, an external `$ref`, and a recursive `$ref` cycle (ours). An omitted
220
+ `additionalProperties` and `anyOf` / `allOf` / internal `$ref` / `$defs` pass. Only the
221
+ `outputConfig` form is checked, not `additionalModelRequestFields.output_config` nor
222
+ `InvokeModel` bodies.
223
+ - **Document names** (`document.name`, [API reference](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_DocumentBlock.html)):
224
+ 1 to 200 characters of ASCII letters, digits, whitespace, hyphens, parentheses and square
225
+ brackets, with no run of two whitespace characters. The message is the one a consumer recorded
226
+ from live Bedrock.
227
+ - **`cachePoint`** ([docs](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html)):
228
+ a `cachePoint` content block directly after a `document` block (consumer report; message ours);
229
+ and, on Converse / ConverseStream, a `cachePoint` in `system`, `messages` or `toolConfig.tools`
230
+ for a model outside the documented list (Claude 3.5 Haiku, 3.5 Sonnet v2, 3.7 Sonnet, Claude
231
+ 4 and later Opus / Sonnet / Haiku, Fable and Mythos; Nova Micro / Lite / Pro / 2 Lite), matched
232
+ on the family inside plain, `us.` / `global.` and ARN forms. The list grows: turn the model
233
+ check off with `PUT /__admin/settings {"validatePromptCaching": false}`. The `ttl` field is
234
+ not checked.
235
+ - **Unknown model ids** still answer normally (`modelId` is any id). A `knownModels` setting that
236
+ makes an unknown id fail was requested in #523 but not built: whether live Bedrock answers
237
+ `ResourceNotFoundException` or `ValidationException` for it has not been observed.
238
+
188
239
  ### Namespaces
189
240
 
190
241
  `x-mockingbird-namespace`, a `/__admin/ns/<name>` prefix on the endpoint URL, or by credential: the SDKs
@@ -213,6 +264,7 @@ cannot add headers, so map each worker's access key id:
213
264
  | `accessKeyCredential` | function | The SigV4 access key id of a request (how credentials map to namespaces). |
214
265
  | `clockSleep` | function | A sleep that waits on a (possibly frozen) emulator clock. |
215
266
  | `titanEmbedding` | function | The deterministic unit vector Titan answers with. |
267
+ | `lexicalEmbedding`, `EMBEDDING_MODES` | function, array | The opt-in similarity-preserving embedding and the `embeddingMode` values. |
216
268
  | `sampleSchema` | function | The minimal instance of a JSON Schema (the unscripted structured output). |
217
269
  | `parseScript` | function | Validate one script (what `PUT /__admin/scripts` runs). |
218
270
  | `MODEL_OPERATIONS`, `STOP_REASONS`, `TURN_FAULTS` | arrays | The values `operation`, `stopReason` and `fault` accept. |
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createRuntime,
3
3
  parseScript
4
- } from "./chunk-LG5SXNQW.js";
4
+ } from "./chunk-PFSY7JLL.js";
5
5
 
6
6
  // src/h2c.ts
7
7
  import {
@@ -338,4 +338,4 @@ export {
338
338
  createServer,
339
339
  serveTarget
340
340
  };
341
- //# sourceMappingURL=chunk-3H4ZVXWL.js.map
341
+ //# sourceMappingURL=chunk-CL6K2ZUM.js.map
@@ -4029,7 +4029,7 @@ var stateAdminRoutes = (routes) => {
4029
4029
  };
4030
4030
 
4031
4031
  // ../core/dist/version.js
4032
- var PACKAGE_VERSION = true ? "3.7.0" : UNRELEASED_VERSION;
4032
+ var PACKAGE_VERSION = true ? "3.8.0" : UNRELEASED_VERSION;
4033
4033
 
4034
4034
  // ../core/dist/signing.js
4035
4035
  var encoder = new TextEncoder();
@@ -5327,6 +5327,9 @@ var ValidationProblem = class extends Error {
5327
5327
  };
5328
5328
  var NO_PREFILL = /claude-(?:sonnet|opus)-4-[5-9]|claude-opus-4-1|claude-(?:sonnet|opus|haiku)-[5-9]/i;
5329
5329
  var ONE_SAMPLING_PARAM = /claude-(?:sonnet|opus|haiku)-4-[5-9]|claude-opus-4-1|claude-(?:sonnet|opus|haiku)-[5-9]/i;
5330
+ var PROMPT_CACHING = /claude-3-5-haiku|claude-3-5-sonnet-20241022|claude-3-7-sonnet|claude-(?:opus|sonnet|haiku|fable|mythos)-[4-9]|nova-(?:micro|lite|pro|2-lite)/i;
5331
+ var DOCUMENT_NAME = /^[a-zA-Z0-9\s\-()[\]]{1,200}$/;
5332
+ var DOCUMENT_NAME_MESSAGE = "The document file name can only contain alphanumeric characters, whitespace characters, hyphens, parentheses, and square brackets. The name can't contain more than one consecutive whitespace character.";
5330
5333
  var parseSchema = (value) => {
5331
5334
  if (typeof value !== "string") return value;
5332
5335
  try {
@@ -5335,6 +5338,64 @@ var parseSchema = (value) => {
5335
5338
  return {};
5336
5339
  }
5337
5340
  };
5341
+ var UNSUPPORTED_KEYWORDS = ["minimum", "maximum", "multipleOf", "minLength", "maxLength"];
5342
+ var SCHEMA_SLOTS = ["items", "not", "if", "then", "else", "contains"];
5343
+ var SCHEMA_LISTS = ["anyOf", "allOf", "prefixItems"];
5344
+ var SCHEMA_MAPS = ["properties", "$defs", "definitions", "patternProperties"];
5345
+ var unsupportedSchemaReason = (root) => {
5346
+ const resolve = (ref) => {
5347
+ if (ref === "#") return root;
5348
+ let node = root;
5349
+ for (const part of ref.slice(2).split("/")) {
5350
+ node = isRecord5(node) ? node[part.replaceAll("~1", "/").replaceAll("~0", "~")] : void 0;
5351
+ }
5352
+ return node;
5353
+ };
5354
+ const visit = (node, refs) => {
5355
+ if (!isRecord5(node)) return void 0;
5356
+ if (node.oneOf !== void 0) return "Schema type 'oneOf' is not supported";
5357
+ for (const key of UNSUPPORTED_KEYWORDS) {
5358
+ if (node[key] !== void 0) return `Schema keyword '${key}' is not supported`;
5359
+ }
5360
+ if (typeof node.minItems === "number" && node.minItems > 1) {
5361
+ return `Schema keyword 'minItems' only supports the values 0 and 1 (got ${node.minItems})`;
5362
+ }
5363
+ if (node.additionalProperties !== void 0 && node.additionalProperties !== false) {
5364
+ return `For 'object' type, 'additionalProperties: ${JSON.stringify(node.additionalProperties)}' is not supported. Please set 'additionalProperties' to false`;
5365
+ }
5366
+ if (Array.isArray(node.type) && Array.isArray(node.enum) && node.enum.length > 0 && node.enum.every((value) => typeof value === "string")) {
5367
+ return `A string-only 'enum' cannot be combined with the union type ${JSON.stringify(node.type)}`;
5368
+ }
5369
+ if (typeof node.$ref === "string") {
5370
+ if (!node.$ref.startsWith("#")) {
5371
+ return `External $ref '${node.$ref}' is not supported; only internal references are`;
5372
+ }
5373
+ if (refs.includes(node.$ref)) return `Recursive schemas are not supported ('${node.$ref}')`;
5374
+ const problem = visit(resolve(node.$ref), [...refs, node.$ref]);
5375
+ if (problem) return problem;
5376
+ }
5377
+ for (const key of SCHEMA_SLOTS) {
5378
+ const problem = visit(node[key], refs);
5379
+ if (problem) return problem;
5380
+ }
5381
+ for (const key of SCHEMA_LISTS) {
5382
+ for (const each of array(node[key])) {
5383
+ const problem = visit(each, refs);
5384
+ if (problem) return problem;
5385
+ }
5386
+ }
5387
+ for (const key of SCHEMA_MAPS) {
5388
+ const map = node[key];
5389
+ if (!isRecord5(map)) continue;
5390
+ for (const each of Object.values(map)) {
5391
+ const problem = visit(each, refs);
5392
+ if (problem) return problem;
5393
+ }
5394
+ }
5395
+ return void 0;
5396
+ };
5397
+ return visit(root, []);
5398
+ };
5338
5399
  var textOf = (blocks) => blocks.map((block) => isRecord5(block) && typeof block.text === "string" ? block.text : "").filter((text2) => text2.length > 0).join("\n");
5339
5400
  var conversationFacts = (messages) => {
5340
5401
  let lastSaid = -1;
@@ -5432,11 +5493,15 @@ var converseMessage = (value, index) => {
5432
5493
  hasCachePoint: false,
5433
5494
  chars: 0
5434
5495
  };
5435
- for (const block of content) {
5496
+ for (const [at, block] of content.entries()) {
5436
5497
  if (!isRecord5(block)) continue;
5437
5498
  out.chars += JSON.stringify(block).length;
5438
5499
  if (typeof block.text === "string") out.hasContent = true;
5439
5500
  if (isRecord5(block.document)) {
5501
+ const name2 = block.document.name;
5502
+ if (typeof name2 === "string" && (!DOCUMENT_NAME.test(name2) || /\s{2,}/.test(name2))) {
5503
+ throw new ValidationProblem(DOCUMENT_NAME_MESSAGE);
5504
+ }
5440
5505
  out.hasDocument = true;
5441
5506
  out.hasContent = true;
5442
5507
  }
@@ -5445,7 +5510,14 @@ var converseMessage = (value, index) => {
5445
5510
  out.hasContent = true;
5446
5511
  }
5447
5512
  if (isRecord5(block.video)) out.hasContent = true;
5448
- if (isRecord5(block.cachePoint)) out.hasCachePoint = true;
5513
+ if (isRecord5(block.cachePoint)) {
5514
+ if (isRecord5(content[at - 1]) && isRecord5(content[at - 1].document)) {
5515
+ throw new ValidationProblem(
5516
+ `The model returned the following errors: messages.${index}.content.${at}: a cachePoint cannot directly follow a document block.`
5517
+ );
5518
+ }
5519
+ out.hasCachePoint = true;
5520
+ }
5449
5521
  if (isRecord5(block.toolUse)) {
5450
5522
  out.toolUses.push({
5451
5523
  id: String(block.toolUse.toolUseId ?? ""),
@@ -5456,7 +5528,7 @@ var converseMessage = (value, index) => {
5456
5528
  }
5457
5529
  return out;
5458
5530
  };
5459
- var analyzeConverse = (operation, modelId, body) => {
5531
+ var analyzeConverse = (operation, modelId, body, options = {}) => {
5460
5532
  if (!isRecord5(body))
5461
5533
  throw new ValidationProblem(
5462
5534
  "Malformed input request, please reformat your input and try again."
@@ -5505,9 +5577,22 @@ var analyzeConverse = (operation, modelId, body) => {
5505
5577
  const jsonSchema = textFormat && isRecord5(textFormat.structure) && isRecord5(textFormat.structure.jsonSchema) ? textFormat.structure.jsonSchema : void 0;
5506
5578
  const nativeFormat = isRecord5(additional.output_config) && isRecord5(additional.output_config.format) ? additional.output_config.format : void 0;
5507
5579
  if (jsonSchema) {
5580
+ let schema = jsonSchema.schema;
5581
+ if (typeof schema === "string") {
5582
+ try {
5583
+ schema = JSON.parse(schema);
5584
+ } catch {
5585
+ schema = void 0;
5586
+ }
5587
+ if (!isRecord5(schema)) {
5588
+ throw new ValidationProblem("The structured output schema is not a valid JSON object.");
5589
+ }
5590
+ }
5591
+ const reason = unsupportedSchemaReason(schema);
5592
+ if (reason) throw new ValidationProblem(reason);
5508
5593
  structured = {
5509
5594
  form: "outputConfig",
5510
- schema: parseSchema(jsonSchema.schema),
5595
+ schema,
5511
5596
  ...typeof jsonSchema.name === "string" ? { name: jsonSchema.name } : {}
5512
5597
  };
5513
5598
  } else if (nativeFormat && nativeFormat.type === "json_schema") {
@@ -5521,6 +5606,12 @@ var analyzeConverse = (operation, modelId, body) => {
5521
5606
  }
5522
5607
  const facts = conversationFacts(messages);
5523
5608
  const systemText = textOf(system);
5609
+ const hasCachePoint = messages.some((m) => m.hasCachePoint) || system.some((b) => isRecord5(b) && isRecord5(b.cachePoint)) || array(toolConfig?.tools).some((t) => isRecord5(t) && isRecord5(t.cachePoint));
5610
+ if (hasCachePoint && options.validatePromptCaching !== false && !PROMPT_CACHING.test(modelId)) {
5611
+ throw new ValidationProblem(
5612
+ "The model does not support prompt caching: remove the cachePoint block from the request."
5613
+ );
5614
+ }
5524
5615
  return {
5525
5616
  operation,
5526
5617
  modelId,
@@ -5531,7 +5622,7 @@ var analyzeConverse = (operation, modelId, body) => {
5531
5622
  toolChoice,
5532
5623
  hasDocument: messages.some((m) => m.hasDocument),
5533
5624
  hasImage: messages.some((m) => m.hasImage),
5534
- hasCachePoint: messages.some((m) => m.hasCachePoint) || system.some((b) => isRecord5(b) && isRecord5(b.cachePoint)) || array(toolConfig?.tools).some((t) => isRecord5(t) && isRecord5(t.cachePoint)),
5625
+ hasCachePoint,
5535
5626
  hasGuardrail: isRecord5(body.guardrailConfig),
5536
5627
  toolResults: facts.toolResults,
5537
5628
  turnIndex: facts.turnIndex,
@@ -5833,6 +5924,7 @@ var TURN_FAULTS = [
5833
5924
  "model_timeout",
5834
5925
  "service_unavailable",
5835
5926
  "internal_server",
5927
+ "resource_not_found",
5836
5928
  "mid_stream_exception",
5837
5929
  "max_tokens",
5838
5930
  "truncated_frame",
@@ -5866,6 +5958,16 @@ var matches2 = (match, call, context) => {
5866
5958
  if (match.systemHash !== void 0 && match.systemHash.toLowerCase() !== context.systemHash) {
5867
5959
  return false;
5868
5960
  }
5961
+ if (match.systemText !== void 0) {
5962
+ if (call.systemText === "" || !textMatches(match.systemText, call.systemText)) return false;
5963
+ }
5964
+ if (match.jsonSchemaName !== void 0) {
5965
+ const name2 = call.structured?.form === "outputConfig" ? call.structured.name : void 0;
5966
+ if (name2 === void 0) return false;
5967
+ const rule = match.jsonSchemaName;
5968
+ if (typeof rule === "string" ? rule.toLowerCase() !== name2.toLowerCase() : !textMatches(rule, name2))
5969
+ return false;
5970
+ }
5869
5971
  if (match.toolsInclude?.some((name2) => !call.tools.includes(name2))) return false;
5870
5972
  if (match.toolChoice !== void 0) {
5871
5973
  const want = ["auto", "any", "none"].includes(match.toolChoice) ? match.toolChoice : `tool:${match.toolChoice}`;
@@ -5938,6 +6040,75 @@ var checkTurn = (turn, path) => {
5938
6040
  }
5939
6041
  return void 0;
5940
6042
  };
6043
+ var MATCH_KEYS = [
6044
+ "modelId",
6045
+ "operation",
6046
+ "lastUserText",
6047
+ "systemHash",
6048
+ "systemText",
6049
+ "jsonSchemaName",
6050
+ "toolsInclude",
6051
+ "toolChoice",
6052
+ "hasDocument",
6053
+ "hasImage",
6054
+ "callIndex"
6055
+ ];
6056
+ var checkTextMatch = (rule, path) => {
6057
+ if (typeof rule === "string") return void 0;
6058
+ if (!isRecord6(rule)) return fail2(path, "a string or {contains, regex, flags}");
6059
+ for (const key of Object.keys(rule)) {
6060
+ if (!["contains", "regex", "flags"].includes(key)) {
6061
+ return fail2(`${path}.${key}`, "unknown text match field (one of contains, regex, flags)");
6062
+ }
6063
+ }
6064
+ for (const key of ["contains", "regex", "flags"]) {
6065
+ if (rule[key] !== void 0 && typeof rule[key] !== "string")
6066
+ return fail2(`${path}.${key}`, "string");
6067
+ }
6068
+ if (typeof rule.regex === "string") {
6069
+ try {
6070
+ new RegExp(rule.regex, typeof rule.flags === "string" ? rule.flags : "i");
6071
+ } catch {
6072
+ return fail2(`${path}.regex`, "not a valid regular expression (with these flags)");
6073
+ }
6074
+ }
6075
+ return void 0;
6076
+ };
6077
+ var checkMatch = (match, path) => {
6078
+ for (const key of Object.keys(match)) {
6079
+ if (!MATCH_KEYS.includes(key)) {
6080
+ return fail2(`${path}.${key}`, `unknown match field (one of ${MATCH_KEYS.join(", ")})`);
6081
+ }
6082
+ }
6083
+ if (match.operation !== void 0) {
6084
+ const ops = Array.isArray(match.operation) ? match.operation : [match.operation];
6085
+ for (const op of ops) {
6086
+ if (!MODEL_OPERATIONS.includes(op)) {
6087
+ return fail2(`${path}.operation`, `one of ${MODEL_OPERATIONS.join(", ")}`);
6088
+ }
6089
+ }
6090
+ }
6091
+ for (const key of ["modelId", "systemHash", "toolChoice"]) {
6092
+ if (match[key] !== void 0 && typeof match[key] !== "string")
6093
+ return fail2(`${path}.${key}`, "string");
6094
+ }
6095
+ for (const key of ["lastUserText", "systemText", "jsonSchemaName"]) {
6096
+ if (match[key] === void 0) continue;
6097
+ const problem = checkTextMatch(match[key], `${path}.${key}`);
6098
+ if (problem) return problem;
6099
+ }
6100
+ for (const key of ["hasDocument", "hasImage"]) {
6101
+ if (match[key] !== void 0 && typeof match[key] !== "boolean")
6102
+ return fail2(`${path}.${key}`, "boolean");
6103
+ }
6104
+ if (match.callIndex !== void 0 && !(typeof match.callIndex === "number" && Number.isInteger(match.callIndex) && match.callIndex >= 0)) {
6105
+ return fail2(`${path}.callIndex`, "a non-negative integer");
6106
+ }
6107
+ if (match.toolsInclude !== void 0 && !(Array.isArray(match.toolsInclude) && match.toolsInclude.every((n) => typeof n === "string"))) {
6108
+ return fail2(`${path}.toolsInclude`, "string[]");
6109
+ }
6110
+ return void 0;
6111
+ };
5941
6112
  var parseScript = (value, index) => {
5942
6113
  const path = `scripts[${index}]`;
5943
6114
  if (!isRecord6(value)) return fail2(path, "a script is an object");
@@ -5952,25 +6123,8 @@ var parseScript = (value, index) => {
5952
6123
  }
5953
6124
  if (value.match !== void 0) {
5954
6125
  if (!isRecord6(value.match)) return fail2(`${path}.match`, "an object");
5955
- const match = value.match;
5956
- if (match.operation !== void 0) {
5957
- const ops = Array.isArray(match.operation) ? match.operation : [match.operation];
5958
- for (const op of ops) {
5959
- if (!MODEL_OPERATIONS.includes(op)) {
5960
- return fail2(`${path}.match.operation`, `one of ${MODEL_OPERATIONS.join(", ")}`);
5961
- }
5962
- }
5963
- }
5964
- if (isRecord6(match.lastUserText) && typeof match.lastUserText.regex === "string") {
5965
- try {
5966
- new RegExp(match.lastUserText.regex);
5967
- } catch {
5968
- return fail2(`${path}.match.lastUserText.regex`, "not a valid regular expression");
5969
- }
5970
- }
5971
- if (match.toolsInclude !== void 0 && !Array.isArray(match.toolsInclude)) {
5972
- return fail2(`${path}.match.toolsInclude`, "string[]");
5973
- }
6126
+ const problem = checkMatch(value.match, `${path}.match`);
6127
+ if (problem) return problem;
5974
6128
  }
5975
6129
  if (value.times !== void 0 && (typeof value.times !== "number" || value.times < 1)) {
5976
6130
  return fail2(`${path}.times`, "a positive count");
@@ -7018,11 +7172,14 @@ var sonicSession = (request, modelId, host) => {
7018
7172
  };
7019
7173
 
7020
7174
  // src/state.ts
7175
+ var EMBEDDING_MODES = ["sha256", "lexical"];
7021
7176
  var DEFAULT_SETTINGS = {
7022
7177
  defaultText: "OK.",
7023
7178
  chunkSize: 16,
7024
7179
  delayMsPerChunk: 0,
7025
- audioTurnChunks: 0
7180
+ audioTurnChunks: 0,
7181
+ embeddingMode: "sha256",
7182
+ validatePromptCaching: true
7026
7183
  };
7027
7184
  var EMPTY_STATS = {
7028
7185
  calls: 0,
@@ -7153,6 +7310,7 @@ var BEDROCK_PRESETS = {
7153
7310
  type: "service_unavailable"
7154
7311
  }),
7155
7312
  access_denied: everyCall("403 AccessDeniedException", { type: "access_denied" }),
7313
+ resource_not_found: everyCall("404 ResourceNotFoundException", { type: "resource_not_found" }),
7156
7314
  internal_server: everyCall("500 InternalServerException", { type: "internal_server" })
7157
7315
  };
7158
7316
  var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
@@ -7175,7 +7333,9 @@ var SETTING_CHECKS = {
7175
7333
  defaultText: (value) => typeof value === "string",
7176
7334
  chunkSize: (value) => typeof value === "number" && value >= 1,
7177
7335
  delayMsPerChunk: (value) => typeof value === "number" && value >= 0,
7178
- audioTurnChunks: (value) => typeof value === "number" && value >= 0
7336
+ audioTurnChunks: (value) => typeof value === "number" && value >= 0,
7337
+ embeddingMode: (value) => EMBEDDING_MODES.includes(value),
7338
+ validatePromptCaching: (value) => typeof value === "boolean"
7179
7339
  };
7180
7340
  var adminRoutes = (runtime) => {
7181
7341
  const store = (replace) => ({ body, namespace }) => {
@@ -7272,6 +7432,11 @@ var FAULT_ERRORS = {
7272
7432
  "ServiceUnavailableException",
7273
7433
  "Bedrock is unable to process your request."
7274
7434
  ],
7435
+ resource_not_found: [
7436
+ 404,
7437
+ "ResourceNotFoundException",
7438
+ "The specified resource ARN was not found. Check the ARN and try again."
7439
+ ],
7275
7440
  internal_server: [
7276
7441
  500,
7277
7442
  "InternalServerException",
@@ -7327,6 +7492,18 @@ var titanEmbedding = async (inputText, dims = 1024) => {
7327
7492
  const norm = Math.sqrt(values.reduce((sum, v) => sum + v * v, 0)) || 1;
7328
7493
  return values.map((v) => v / norm);
7329
7494
  };
7495
+ var lexicalEmbedding = async (inputText, dims = 1024) => {
7496
+ const words = inputText.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
7497
+ if (words.length === 0) return titanEmbedding(inputText, dims);
7498
+ const values = new Array(dims).fill(0);
7499
+ for (const word of words) {
7500
+ const digest = new DataView(await crypto.subtle.digest("SHA-256", utf84.encode(word)));
7501
+ const at = digest.getUint32(0, false) % dims;
7502
+ values[at] = values[at] + (digest.getUint8(4) & 1 ? 1 : -1);
7503
+ }
7504
+ const norm = Math.sqrt(values.reduce((sum, v) => sum + v * v, 0));
7505
+ return norm === 0 ? titanEmbedding(inputText, dims) : values.map((v) => v / norm);
7506
+ };
7330
7507
  var BedrockAPI = class {
7331
7508
  app;
7332
7509
  sqlite;
@@ -7523,7 +7700,9 @@ var BedrockAPI = class {
7523
7700
  }
7524
7701
  let call;
7525
7702
  try {
7526
- call = analyzeConverse(operation, modelId, body);
7703
+ call = analyzeConverse(operation, modelId, body, {
7704
+ validatePromptCaching: this.state.current().validatePromptCaching
7705
+ });
7527
7706
  } catch (error) {
7528
7707
  if (error instanceof ValidationProblem)
7529
7708
  return bedrockError(400, "ValidationException", error.message, requestId);
@@ -7627,7 +7806,8 @@ var BedrockAPI = class {
7627
7806
  ids: { modelId, script: "unscripted:titan", inputTokens: String(inputTextTokenCount) }
7628
7807
  });
7629
7808
  if (failed) return notes2(failed);
7630
- const embedding = await titanEmbedding(inputText, dimensions);
7809
+ const embed = this.state.current().embeddingMode === "lexical" ? lexicalEmbedding : titanEmbedding;
7810
+ const embedding = await embed(inputText, dimensions);
7631
7811
  return notes2(
7632
7812
  jsonRes(
7633
7813
  200,
@@ -7780,6 +7960,7 @@ export {
7780
7960
  eventFrame,
7781
7961
  exceptionFrame,
7782
7962
  readFrames,
7963
+ EMBEDDING_MODES,
7783
7964
  DEFAULT_SETTINGS,
7784
7965
  BEDROCK_PRESETS,
7785
7966
  createRuntime2 as createRuntime,
@@ -7788,6 +7969,7 @@ export {
7788
7969
  accessKeyCredential,
7789
7970
  clockSleep,
7790
7971
  titanEmbedding,
7972
+ lexicalEmbedding,
7791
7973
  BedrockAPI
7792
7974
  };
7793
- //# sourceMappingURL=chunk-LG5SXNQW.js.map
7975
+ //# sourceMappingURL=chunk-PFSY7JLL.js.map