{"record":{"id":"475e303bb1a8288a","repo":"moeru-ai/airi","slug":"aliyun-nls-returned-a-non-streaming-result-unexpec","errorCode":null,"errorMessage":"Aliyun NLS returned a non-streaming result unexpectedly.","messagePattern":"Aliyun NLS returned a non-streaming result unexpectedly\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/stage-pages/src/pages/settings/providers/transcription/aliyun-nls-transcription.vue","lineNumber":260,"sourceCode":"            },\n          },\n          onSessionTerminated: async (error?: unknown) => {\n            if (error)\n              errorMessage.value = errorMessageFromValue(error)\n            isStreaming.value = false\n            transcriptionAbortController.value = undefined\n          },\n          sessionOptions: {\n            format: 'pcm',\n            sample_rate: SAMPLE_RATE,\n            enable_punctuation_prediction: true,\n          },\n        },\n      },\n    )\n\n    if (result.mode !== 'stream')\n      throw new Error('Aliyun NLS returned a non-streaming result unexpectedly.')\n\n    activeTranscription.value = result\n    transcriptionTextPromise.value = result.text\n      .catch((error) => {\n        errorMessage.value = errorMessageFromValue(error)\n        throw error\n      })\n\n    const stream = await navigator.mediaDevices.getUserMedia({\n      audio: {\n        channelCount: 1,\n        sampleRate: SAMPLE_RATE,\n        echoCancellation: true,\n        noiseSuppression: true,\n        autoGainControl: true,\n      },\n    })\n","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-pages/src/pages/settings/providers/transcription/aliyun-nls-transcription.vue#L242-L278","documentation":"Invariant assertion in the Aliyun NLS streaming playground: `hearingStore.transcription(...)` returns a discriminated union (`HearingTranscriptionResult`) whose `mode` is 'stream' when the realtime path was taken (packages/stage-ui/src/stores/modules/hearing.ts:459-493 requires stream capabilities, a resolved stream executor, and the `inputAudioStream` the page passes) and 'generate' for the buffered path. The page then does `result.text` plumbing and PCM streaming, which only exists for 'stream', so any other mode is rejected loudly instead of being mis-consumed.","triggerScenarios":"The stream executor not resolving for the provider id (`resolveStreamTranscriptionExecutor` returning null) while buffered generation still runs — e.g. capability metadata drift between the provider definition and the runtime; a refactor/change making `getTranscriptionFeatures` report streamOutput false so the call falls into the buffered branch; a File input sneaking in alongside stream expectations; future providers reusing this page without stream support.","commonSituations":"Definition capabilities edited (streamOutput/streamInput flags) without updating the page; custom provider ids colliding with the executor registry; version skew between stage-ui store and stage-pages during upgrades; tests exercising the page with a mocked buffered transcription result.","solutions":["Confirm the provider definition still declares transcription capabilities streamOutput: true and streamInput: true (as aliyun-nls does) and that the page passes `{ inputAudioStream }`.","Check `resolveStreamTranscriptionExecutor(providerId)` in the console/debugger — a null executor is the usual reason the store fell back to buffered mode.","Update or align stage-ui and stage-pages to the same version so feature detection and the page contract match.","If buffered results are acceptable for your flow, handle `mode === 'generate'` explicitly (read `result.text`/segments) instead of throwing.","Report the mismatch as a bug — this assertion exists to expose exactly this drift."],"exampleFix":"// before\nif (result.mode !== 'stream')\n  throw new Error('Aliyun NLS returned a non-streaming result unexpectedly.')\n\n// after (keep the invariant, but include diagnosis hints)\nif (result.mode !== 'stream')\n  throw new Error(`Aliyun NLS returned mode '${result.mode}' — streaming capabilities/executor drifted. Check provider definition streamOutput/streamInput and resolveStreamTranscriptionExecutor('${providerId}').`)","handlingStrategy":"type-guard","validationCode":"const features = providersStore.getTranscriptionFeatures(providerId)\nif (!features.supportsStreamOutput || !features.supportsStreamInput)\n  throw new Error('This page requires a provider with streaming transcription capabilities')","typeGuard":"function isStreamTranscriptionResult(r: HearingTranscriptionResult): r is Extract<HearingTranscriptionResult, { mode: 'stream' }> {\n  return r.mode === 'stream'\n}\n// usage: if (!isStreamTranscriptionResult(result)) throw new Error('...')","tryCatchPattern":"try {\n  const result = await hearingStore.transcription(providerId, provider, defaultModel, { inputAudioStream: audioStream }, undefined, { ... })\n  if (result.mode !== 'stream') throw new Error('unexpected buffered result')\n}\ncatch (error) { errorMessage.value = errorMessageFromValue(error) }","preventionTips":["Keep provider capability metadata (streamInput/streamOutput) and page expectations in sync — this assertion exists to catch drift.","Narrow the result union with a type guard before consuming stream-only fields.","When reusing this page for new providers, verify the stream executor resolves for the provider id first."],"tags":["transcription","streaming","discriminated-union","invariant","aliyun"],"backgroundTag":"response-mode-mismatch","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}