| 1 | import * as IAM from "@/AWS/IAM"; |
| 2 | import * as Lambda from "@/AWS/Lambda"; |
| 3 | import * as Transcribe from "@/AWS/Transcribe"; |
| 4 | import * as Duration from "effect/Duration"; |
| 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as Layer from "effect/Layer"; |
| 7 | import * as Stream from "effect/Stream"; |
| 8 | import { HttpServerRequest } from "effect/http/HttpServerRequest"; |
| 9 | import * as HttpServerResponse from "effect/http/HttpServerResponse"; |
| 10 | import path from "pathe"; |
| 11 | |
| 12 | const main = path.resolve(import.meta.dirname, "handler.ts"); |
| 13 | |
| 14 | // An S3 URI that fails Transcribe's server-side pattern validation — drives |
| 15 | // every Start*/Create* binding through its typed BadRequestException path |
| 16 | // without actually starting (and paying for) a job or leaving anything |
| 17 | // behind to clean up. |
| 18 | const INVALID_S3_URI = "invalid-uri"; |
| 19 | |
| 20 | export class TranscribeTestFunction extends Lambda.Function<Lambda.Function>()( |
| 21 | "TranscribeTestFunction", |
| 22 | ) {} |
| 23 | |
| 24 | export default TranscribeTestFunction.make( |
| 25 | { |
| 26 | main, |
| 27 | functionUrl: true, |
| 28 | // Transcribe control-plane calls routinely take a couple of seconds; |
| 29 | // AWS's default 3s Lambda timeout is too tight under cold starts. |
| 30 | timeout: Duration.seconds(30), |
| 31 | }, |
| 32 | Effect.gen(function* () { |
| 33 | // The role Amazon Transcribe assumes to read S3 media for the role-bound |
| 34 | // bindings (StartCallAnalyticsJob, StartMedicalScribeJob, |
| 35 | // CreateLanguageModel). Requests here never get past validation, so the |
| 36 | // role needs no real S3 grants — only the transcribe.amazonaws.com trust. |
| 37 | const dataAccessRole = yield* IAM.Role("TranscribeDataAccessRole", { |
| 38 | assumeRolePolicyDocument: { |
| 39 | Version: "2012-10-17", |
| 40 | Statement: [ |
| 41 | { |
| 42 | Effect: "Allow", |
| 43 | Principal: { Service: "transcribe.amazonaws.com" }, |
| 44 | Action: ["sts:AssumeRole"], |
| 45 | }, |
| 46 | ], |
| 47 | }, |
| 48 | }); |
| 49 | |
| 50 | // --- batch transcription jobs --- |
| 51 | const startJob = yield* Transcribe.StartTranscriptionJob(); |
| 52 | const getJob = yield* Transcribe.GetTranscriptionJob(); |
| 53 | const listJobs = yield* Transcribe.ListTranscriptionJobs(); |
| 54 | const deleteJob = yield* Transcribe.DeleteTranscriptionJob(); |
| 55 | |
| 56 | // --- call analytics jobs --- |
| 57 | const startCallJob = |
| 58 | yield* Transcribe.StartCallAnalyticsJob(dataAccessRole); |
| 59 | const getCallJob = yield* Transcribe.GetCallAnalyticsJob(); |
| 60 | const listCallJobs = yield* Transcribe.ListCallAnalyticsJobs(); |
| 61 | const deleteCallJob = yield* Transcribe.DeleteCallAnalyticsJob(); |
| 62 | |
| 63 | // --- medical transcription jobs --- |
| 64 | const startMedicalJob = yield* Transcribe.StartMedicalTranscriptionJob(); |
| 65 | const getMedicalJob = yield* Transcribe.GetMedicalTranscriptionJob(); |
| 66 | const listMedicalJobs = yield* Transcribe.ListMedicalTranscriptionJobs(); |
| 67 | const deleteMedicalJob = yield* Transcribe.DeleteMedicalTranscriptionJob(); |
| 68 | |
| 69 | // --- medical scribe jobs --- |
| 70 | const startScribeJob = |
| 71 | yield* Transcribe.StartMedicalScribeJob(dataAccessRole); |
| 72 | const getScribeJob = yield* Transcribe.GetMedicalScribeJob(); |
| 73 | const listScribeJobs = yield* Transcribe.ListMedicalScribeJobs(); |
| 74 | const deleteScribeJob = yield* Transcribe.DeleteMedicalScribeJob(); |
| 75 | |
| 76 | // --- custom vocabularies --- |
| 77 | const createVocabulary = yield* Transcribe.CreateVocabulary(); |
| 78 | const getVocabulary = yield* Transcribe.GetVocabulary(); |
| 79 | const updateVocabulary = yield* Transcribe.UpdateVocabulary(); |
| 80 | const deleteVocabulary = yield* Transcribe.DeleteVocabulary(); |
| 81 | const listVocabularies = yield* Transcribe.ListVocabularies(); |
| 82 | |
| 83 | // --- vocabulary filters --- |
| 84 | const createFilter = yield* Transcribe.CreateVocabularyFilter(); |
| 85 | const getFilter = yield* Transcribe.GetVocabularyFilter(); |
| 86 | const updateFilter = yield* Transcribe.UpdateVocabularyFilter(); |
| 87 | const deleteFilter = yield* Transcribe.DeleteVocabularyFilter(); |
| 88 | const listFilters = yield* Transcribe.ListVocabularyFilters(); |
| 89 | |
| 90 | // --- medical vocabularies --- |
| 91 | const createMedicalVocabulary = yield* Transcribe.CreateMedicalVocabulary(); |
| 92 | const getMedicalVocabulary = yield* Transcribe.GetMedicalVocabulary(); |
| 93 | const updateMedicalVocabulary = yield* Transcribe.UpdateMedicalVocabulary(); |
| 94 | const deleteMedicalVocabulary = yield* Transcribe.DeleteMedicalVocabulary(); |
| 95 | const listMedicalVocabularies = yield* Transcribe.ListMedicalVocabularies(); |
| 96 | |
| 97 | // --- call analytics categories --- |
| 98 | const createCategory = yield* Transcribe.CreateCallAnalyticsCategory(); |
| 99 | const getCategory = yield* Transcribe.GetCallAnalyticsCategory(); |
| 100 | const updateCategory = yield* Transcribe.UpdateCallAnalyticsCategory(); |
| 101 | const deleteCategory = yield* Transcribe.DeleteCallAnalyticsCategory(); |
| 102 | const listCategories = yield* Transcribe.ListCallAnalyticsCategories(); |
| 103 | |
| 104 | // --- custom language models --- |
| 105 | const createLanguageModel = |
| 106 | yield* Transcribe.CreateLanguageModel(dataAccessRole); |
| 107 | const describeLanguageModel = yield* Transcribe.DescribeLanguageModel(); |
| 108 | const listLanguageModels = yield* Transcribe.ListLanguageModels(); |
| 109 | const deleteLanguageModel = yield* Transcribe.DeleteLanguageModel(); |
| 110 | |
| 111 | // --- tagging --- |
| 112 | const tagResource = yield* Transcribe.TagResource(); |
| 113 | const untagResource = yield* Transcribe.UntagResource(); |
| 114 | const listTagsForResource = yield* Transcribe.ListTagsForResource(); |
| 115 | |
| 116 | // Deploy-time: creates the EventBridge rule (default bus, source |
| 117 | // aws.transcribe) targeting this Function. The test verifies the rule |
| 118 | // deploys; runtime firing would require a real transcription over S3 |
| 119 | // media. |
| 120 | yield* Transcribe.consumeTranscriptionJobEvents( |
| 121 | { statuses: ["COMPLETED", "FAILED"] }, |
| 122 | (events) => |
| 123 | Stream.runForEach(events, (event) => |
| 124 | Effect.log( |
| 125 | `transcribe job event: ${event.detail.TranscriptionJobName} -> ${event.detail.TranscriptionJobStatus}`, |
| 126 | ), |
| 127 | ), |
| 128 | ); |
| 129 | |
| 130 | // Normalize every binding call to { ok, error?, ...extra } so routes can |
| 131 | // assert typed tags without 500s. |
| 132 | const result = <A, E extends { _tag: string }>( |
| 133 | effect: Effect.Effect<A, E>, |
| 134 | extract?: (a: A) => Record<string, unknown>, |
| 135 | ) => |
| 136 | effect.pipe( |
| 137 | Effect.map((a) => ({ |
| 138 | ok: true, |
| 139 | error: undefined as string | undefined, |
| 140 | ...(extract ? extract(a) : {}), |
| 141 | })), |
| 142 | Effect.catch((e) => |
| 143 | Effect.succeed({ ok: false, error: e._tag as string }), |
| 144 | ), |
| 145 | ); |
| 146 | |
| 147 | return { |
| 148 | fetch: Effect.gen(function* () { |
| 149 | const request = yield* HttpServerRequest; |
| 150 | const url = new URL(request.originalUrl); |
| 151 | const route = `${request.method} ${url.pathname}`; |
| 152 | const name = url.searchParams.get("name") ?? ""; |
| 153 | const body = |
| 154 | request.method === "POST" |
| 155 | ? ((yield* request.json) as Record<string, string>) |
| 156 | : {}; |
| 157 | |
| 158 | const respond = ( |
| 159 | effect: Effect.Effect<Record<string, unknown>, never>, |
| 160 | ) => effect.pipe(Effect.flatMap(HttpServerResponse.json)); |
| 161 | |
| 162 | switch (route) { |
| 163 | // --- lists (count proves grant + wire-up) --- |
| 164 | case "GET /jobs": |
| 165 | return yield* respond( |
| 166 | result(listJobs({ MaxResults: 5 }), (r) => ({ |
| 167 | count: r.TranscriptionJobSummaries?.length ?? 0, |
| 168 | })), |
| 169 | ); |
| 170 | case "GET /callJobs": |
| 171 | return yield* respond( |
| 172 | result(listCallJobs({ MaxResults: 5 }), (r) => ({ |
| 173 | count: r.CallAnalyticsJobSummaries?.length ?? 0, |
| 174 | })), |
| 175 | ); |
| 176 | case "GET /medicalJobs": |
| 177 | return yield* respond( |
| 178 | result(listMedicalJobs({ MaxResults: 5 }), (r) => ({ |
| 179 | count: r.MedicalTranscriptionJobSummaries?.length ?? 0, |
| 180 | })), |
| 181 | ); |
| 182 | case "GET /scribeJobs": |
| 183 | return yield* respond( |
| 184 | result(listScribeJobs({ MaxResults: 5 }), (r) => ({ |
| 185 | count: r.MedicalScribeJobSummaries?.length ?? 0, |
| 186 | })), |
| 187 | ); |
| 188 | case "GET /vocabularies": |
| 189 | return yield* respond( |
| 190 | result(listVocabularies({ MaxResults: 5 }), (r) => ({ |
| 191 | count: r.Vocabularies?.length ?? 0, |
| 192 | })), |
| 193 | ); |
| 194 | case "GET /medicalVocabularies": |
| 195 | return yield* respond( |
| 196 | result(listMedicalVocabularies({ MaxResults: 5 }), (r) => ({ |
| 197 | count: r.Vocabularies?.length ?? 0, |
| 198 | })), |
| 199 | ); |
| 200 | case "GET /filters": |
| 201 | return yield* respond( |
| 202 | result(listFilters({ MaxResults: 5 }), (r) => ({ |
| 203 | count: r.VocabularyFilters?.length ?? 0, |
| 204 | })), |
| 205 | ); |
| 206 | case "GET /categories": |
| 207 | return yield* respond( |
| 208 | result(listCategories({ MaxResults: 5 }), (r) => ({ |
| 209 | count: r.Categories?.length ?? 0, |
| 210 | })), |
| 211 | ); |
| 212 | case "GET /languageModels": |
| 213 | return yield* respond( |
| 214 | result(listLanguageModels({ MaxResults: 5 }), (r) => ({ |
| 215 | count: r.Models?.length ?? 0, |
| 216 | })), |
| 217 | ); |
| 218 | |
| 219 | // --- reads (typed not-found tags for bogus names) --- |
| 220 | case "GET /job": |
| 221 | return yield* respond( |
| 222 | result(getJob({ TranscriptionJobName: name }), (r) => ({ |
| 223 | status: r.TranscriptionJob?.TranscriptionJobStatus, |
| 224 | })), |
| 225 | ); |
| 226 | case "GET /callJob": |
| 227 | return yield* respond( |
| 228 | result(getCallJob({ CallAnalyticsJobName: name }), (r) => ({ |
| 229 | status: r.CallAnalyticsJob?.CallAnalyticsJobStatus, |
| 230 | })), |
| 231 | ); |
| 232 | case "GET /medicalJob": |
| 233 | return yield* respond( |
| 234 | result( |
| 235 | getMedicalJob({ MedicalTranscriptionJobName: name }), |
| 236 | (r) => ({ |
| 237 | status: r.MedicalTranscriptionJob?.TranscriptionJobStatus, |
| 238 | }), |
| 239 | ), |
| 240 | ); |
| 241 | case "GET /scribeJob": |
| 242 | return yield* respond( |
| 243 | result(getScribeJob({ MedicalScribeJobName: name }), (r) => ({ |
| 244 | status: r.MedicalScribeJob?.MedicalScribeJobStatus, |
| 245 | })), |
| 246 | ); |
| 247 | case "GET /vocabulary": |
| 248 | return yield* respond( |
| 249 | result(getVocabulary({ VocabularyName: name }), (r) => ({ |
| 250 | state: r.VocabularyState, |
| 251 | })), |
| 252 | ); |
| 253 | case "GET /medicalVocabulary": |
| 254 | return yield* respond( |
| 255 | result(getMedicalVocabulary({ VocabularyName: name }), (r) => ({ |
| 256 | state: r.VocabularyState, |
| 257 | })), |
| 258 | ); |
| 259 | case "GET /languageModel": |
| 260 | return yield* respond( |
| 261 | result(describeLanguageModel({ ModelName: name }), (r) => ({ |
| 262 | status: r.LanguageModel?.ModelStatus, |
| 263 | })), |
| 264 | ); |
| 265 | |
| 266 | // --- deletes (typed tags for bogus names; real delete for fixtures) --- |
| 267 | case "POST /job/delete": |
| 268 | return yield* respond( |
| 269 | result(deleteJob({ TranscriptionJobName: body.name! })), |
| 270 | ); |
| 271 | case "POST /callJob/delete": |
| 272 | return yield* respond( |
| 273 | result(deleteCallJob({ CallAnalyticsJobName: body.name! })), |
| 274 | ); |
| 275 | case "POST /medicalJob/delete": |
| 276 | return yield* respond( |
| 277 | result( |
| 278 | deleteMedicalJob({ MedicalTranscriptionJobName: body.name! }), |
| 279 | ), |
| 280 | ); |
| 281 | case "POST /scribeJob/delete": |
| 282 | return yield* respond( |
| 283 | result(deleteScribeJob({ MedicalScribeJobName: body.name! })), |
| 284 | ); |
| 285 | case "POST /vocabulary/delete": |
| 286 | return yield* respond( |
| 287 | result(deleteVocabulary({ VocabularyName: body.name! })), |
| 288 | ); |
| 289 | case "POST /medicalVocabulary/delete": |
| 290 | return yield* respond( |
| 291 | result(deleteMedicalVocabulary({ VocabularyName: body.name! })), |
| 292 | ); |
| 293 | case "POST /languageModel/delete": |
| 294 | return yield* respond( |
| 295 | result(deleteLanguageModel({ ModelName: body.name! })), |
| 296 | ); |
| 297 | |
| 298 | // --- starts/creates driven through typed validation failures |
| 299 | // (invalid S3 URI: nothing is created, nothing is billed) --- |
| 300 | case "POST /startJob": |
| 301 | return yield* respond( |
| 302 | result( |
| 303 | startJob({ |
| 304 | TranscriptionJobName: body.name!, |
| 305 | LanguageCode: "en-US", |
| 306 | Media: { MediaFileUri: INVALID_S3_URI }, |
| 307 | }), |
| 308 | ), |
| 309 | ); |
| 310 | case "POST /startCall": |
| 311 | return yield* respond( |
| 312 | result( |
| 313 | startCallJob({ |
| 314 | CallAnalyticsJobName: body.name!, |
| 315 | Media: { MediaFileUri: INVALID_S3_URI }, |
| 316 | }), |
| 317 | ), |
| 318 | ); |
| 319 | case "POST /startMedical": |
| 320 | return yield* respond( |
| 321 | result( |
| 322 | startMedicalJob({ |
| 323 | MedicalTranscriptionJobName: body.name!, |
| 324 | LanguageCode: "en-US", |
| 325 | Media: { MediaFileUri: INVALID_S3_URI }, |
| 326 | OutputBucketName: "alchemy-nonexistent-output", |
| 327 | Specialty: "PRIMARYCARE", |
| 328 | Type: "DICTATION", |
| 329 | }), |
| 330 | ), |
| 331 | ); |
| 332 | case "POST /startScribe": |
| 333 | return yield* respond( |
| 334 | result( |
| 335 | startScribeJob({ |
| 336 | MedicalScribeJobName: body.name!, |
| 337 | Media: { MediaFileUri: INVALID_S3_URI }, |
| 338 | OutputBucketName: "alchemy-nonexistent-output", |
| 339 | Settings: { ShowSpeakerLabels: true, MaxSpeakerLabels: 2 }, |
| 340 | }), |
| 341 | ), |
| 342 | ); |
| 343 | case "POST /vocabulary/create": |
| 344 | return yield* respond( |
| 345 | result( |
| 346 | createVocabulary({ |
| 347 | VocabularyName: body.name!, |
| 348 | LanguageCode: "en-US", |
| 349 | VocabularyFileUri: INVALID_S3_URI, |
| 350 | }), |
| 351 | ), |
| 352 | ); |
| 353 | case "POST /vocabulary/update": |
| 354 | return yield* respond( |
| 355 | result( |
| 356 | updateVocabulary({ |
| 357 | VocabularyName: body.name!, |
| 358 | LanguageCode: "en-US", |
| 359 | Phrases: ["alchemy"], |
| 360 | }), |
| 361 | ), |
| 362 | ); |
| 363 | case "POST /medicalVocabulary/create": |
| 364 | return yield* respond( |
| 365 | result( |
| 366 | createMedicalVocabulary({ |
| 367 | VocabularyName: body.name!, |
| 368 | LanguageCode: "en-US", |
| 369 | VocabularyFileUri: INVALID_S3_URI, |
| 370 | }), |
| 371 | ), |
| 372 | ); |
| 373 | case "POST /medicalVocabulary/update": |
| 374 | return yield* respond( |
| 375 | result( |
| 376 | updateMedicalVocabulary({ |
| 377 | VocabularyName: body.name!, |
| 378 | LanguageCode: "en-US", |
| 379 | VocabularyFileUri: INVALID_S3_URI, |
| 380 | }), |
| 381 | ), |
| 382 | ); |
| 383 | case "POST /languageModel/create": |
| 384 | return yield* respond( |
| 385 | result( |
| 386 | createLanguageModel({ |
| 387 | ModelName: body.name!, |
| 388 | BaseModelName: "NarrowBand", |
| 389 | LanguageCode: "en-US", |
| 390 | InputDataConfig: { S3Uri: INVALID_S3_URI }, |
| 391 | }), |
| 392 | ), |
| 393 | ); |
| 394 | |
| 395 | // --- vocabulary filter lifecycle (real create/get/update/delete) --- |
| 396 | case "POST /filter/create": |
| 397 | return yield* respond( |
| 398 | result( |
| 399 | createFilter({ |
| 400 | VocabularyFilterName: body.name!, |
| 401 | LanguageCode: "en-US", |
| 402 | Words: ["alchemyzz", "distilledzz"], |
| 403 | }), |
| 404 | (r) => ({ name: r.VocabularyFilterName }), |
| 405 | ), |
| 406 | ); |
| 407 | case "GET /filter": |
| 408 | return yield* respond( |
| 409 | result(getFilter({ VocabularyFilterName: name }), (r) => ({ |
| 410 | name: r.VocabularyFilterName, |
| 411 | downloadUri: r.DownloadUri, |
| 412 | })), |
| 413 | ); |
| 414 | case "POST /filter/update": |
| 415 | return yield* respond( |
| 416 | result( |
| 417 | updateFilter({ |
| 418 | VocabularyFilterName: body.name!, |
| 419 | Words: ["alchemyzz", "distilledzz", "workerdzz"], |
| 420 | }), |
| 421 | ), |
| 422 | ); |
| 423 | case "POST /filter/delete": |
| 424 | return yield* respond( |
| 425 | result(deleteFilter({ VocabularyFilterName: body.name! })), |
| 426 | ); |
| 427 | |
| 428 | // --- call analytics category lifecycle --- |
| 429 | case "POST /category/create": |
| 430 | return yield* respond( |
| 431 | result( |
| 432 | createCategory({ |
| 433 | CategoryName: body.name!, |
| 434 | Rules: [{ NonTalkTimeFilter: { Threshold: 30000 } }], |
| 435 | }), |
| 436 | (r) => ({ name: r.CategoryProperties?.CategoryName }), |
| 437 | ), |
| 438 | ); |
| 439 | case "GET /category": |
| 440 | return yield* respond( |
| 441 | result(getCategory({ CategoryName: name }), (r) => ({ |
| 442 | rules: r.CategoryProperties?.Rules?.length ?? 0, |
| 443 | })), |
| 444 | ); |
| 445 | case "POST /category/update": |
| 446 | return yield* respond( |
| 447 | result( |
| 448 | updateCategory({ |
| 449 | CategoryName: body.name!, |
| 450 | Rules: [{ NonTalkTimeFilter: { Threshold: 60000 } }], |
| 451 | }), |
| 452 | (r) => ({ rules: r.CategoryProperties?.Rules?.length ?? 0 }), |
| 453 | ), |
| 454 | ); |
| 455 | case "POST /category/delete": |
| 456 | return yield* respond( |
| 457 | result(deleteCategory({ CategoryName: body.name! })), |
| 458 | ); |
| 459 | |
| 460 | // --- tagging (ARN supplied by the test) --- |
| 461 | case "POST /tag": |
| 462 | return yield* respond( |
| 463 | result( |
| 464 | tagResource({ |
| 465 | ResourceArn: body.arn!, |
| 466 | Tags: [{ Key: "alchemy-test", Value: "1" }], |
| 467 | }), |
| 468 | ), |
| 469 | ); |
| 470 | case "GET /tags": |
| 471 | return yield* respond( |
| 472 | result( |
| 473 | listTagsForResource({ |
| 474 | ResourceArn: url.searchParams.get("arn") ?? "", |
| 475 | }), |
| 476 | (r) => ({ count: r.Tags?.length ?? 0 }), |
| 477 | ), |
| 478 | ); |
| 479 | case "POST /untag": |
| 480 | return yield* respond( |
| 481 | result( |
| 482 | untagResource({ |
| 483 | ResourceArn: body.arn!, |
| 484 | TagKeys: ["alchemy-test"], |
| 485 | }), |
| 486 | ), |
| 487 | ); |
| 488 | |
| 489 | default: |
| 490 | return yield* HttpServerResponse.json( |
| 491 | { error: "Not found", route }, |
| 492 | { status: 404 }, |
| 493 | ); |
| 494 | } |
| 495 | }).pipe(Effect.orDie), |
| 496 | }; |
| 497 | }).pipe( |
| 498 | Effect.provide( |
| 499 | Layer.mergeAll( |
| 500 | Lambda.EventSource, |
| 501 | Layer.mergeAll( |
| 502 | Transcribe.StartTranscriptionJobHttp, |
| 503 | Transcribe.GetTranscriptionJobHttp, |
| 504 | Transcribe.ListTranscriptionJobsHttp, |
| 505 | Transcribe.DeleteTranscriptionJobHttp, |
| 506 | Transcribe.StartCallAnalyticsJobHttp, |
| 507 | Transcribe.GetCallAnalyticsJobHttp, |
| 508 | Transcribe.ListCallAnalyticsJobsHttp, |
| 509 | Transcribe.DeleteCallAnalyticsJobHttp, |
| 510 | Transcribe.StartMedicalTranscriptionJobHttp, |
| 511 | Transcribe.GetMedicalTranscriptionJobHttp, |
| 512 | Transcribe.ListMedicalTranscriptionJobsHttp, |
| 513 | Transcribe.DeleteMedicalTranscriptionJobHttp, |
| 514 | Transcribe.StartMedicalScribeJobHttp, |
| 515 | Transcribe.GetMedicalScribeJobHttp, |
| 516 | Transcribe.ListMedicalScribeJobsHttp, |
| 517 | Transcribe.DeleteMedicalScribeJobHttp, |
| 518 | ), |
| 519 | Layer.mergeAll( |
| 520 | Transcribe.CreateVocabularyHttp, |
| 521 | Transcribe.GetVocabularyHttp, |
| 522 | Transcribe.UpdateVocabularyHttp, |
| 523 | Transcribe.DeleteVocabularyHttp, |
| 524 | Transcribe.ListVocabulariesHttp, |
| 525 | Transcribe.CreateVocabularyFilterHttp, |
| 526 | Transcribe.GetVocabularyFilterHttp, |
| 527 | Transcribe.UpdateVocabularyFilterHttp, |
| 528 | Transcribe.DeleteVocabularyFilterHttp, |
| 529 | Transcribe.ListVocabularyFiltersHttp, |
| 530 | Transcribe.CreateMedicalVocabularyHttp, |
| 531 | Transcribe.GetMedicalVocabularyHttp, |
| 532 | Transcribe.UpdateMedicalVocabularyHttp, |
| 533 | Transcribe.DeleteMedicalVocabularyHttp, |
| 534 | Transcribe.ListMedicalVocabulariesHttp, |
| 535 | ), |
| 536 | Layer.mergeAll( |
| 537 | Transcribe.CreateCallAnalyticsCategoryHttp, |
| 538 | Transcribe.GetCallAnalyticsCategoryHttp, |
| 539 | Transcribe.UpdateCallAnalyticsCategoryHttp, |
| 540 | Transcribe.DeleteCallAnalyticsCategoryHttp, |
| 541 | Transcribe.ListCallAnalyticsCategoriesHttp, |
| 542 | Transcribe.CreateLanguageModelHttp, |
| 543 | Transcribe.DescribeLanguageModelHttp, |
| 544 | Transcribe.ListLanguageModelsHttp, |
| 545 | Transcribe.DeleteLanguageModelHttp, |
| 546 | Transcribe.TagResourceHttp, |
| 547 | Transcribe.UntagResourceHttp, |
| 548 | Transcribe.ListTagsForResourceHttp, |
| 549 | ), |
| 550 | ), |
| 551 | ), |
| 552 | ), |
| 553 | ); |