Skip to content

fix: launch readiness for @interfaze/langchain and langchain-interfaze - #2

Merged
Khurdhula-Harshavardhan merged 28 commits into
mainfrom
fix/launch-blockers-and-live-test-suites
Aug 10, 2026
Merged

fix: launch readiness for @interfaze/langchain and langchain-interfaze#2
Khurdhula-Harshavardhan merged 28 commits into
mainfrom
fix/launch-blockers-and-live-test-suites

Conversation

@Khurdhula-Harshavardhan

@Khurdhula-Harshavardhan Khurdhula-Harshavardhan commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Launch-readiness pass over both packages, audited against the interfaze npm/pip SDKs and the live /v1/chat/completions contract. Every fix was reproduced against the real API before and after.

Correctness

  • reasoningEffort never reached the wire in JS — @langchain/openai drops it for models outside its own heuristic, so reasoning was unreachable despite being documented
  • precontext constructor field was a no-op; the server strips unknown top-level body keys
  • {type:"video", file_id} was a guaranteed 400; now a clear client-side error
  • Video URLs sent no format, unlike inputs.video()
  • Everything reported as OpenAI: _llm_type, ls_provider, lc_secrets, lc_namespace, model_provider
  • Python leaked raw <think> to token callbacks on the v2-protocol path
  • Python streams carried no usage_metadata; JS already defaulted it on
  • Python reasoning={...} crashed every request with a TypeError
  • A falsy wire value suppressed the real tag-derived reasoning
  • vcache merged to int 1 when it flipped mid-stream
  • Role-less deltas leaked <think> and failed isAIMessage() — now normalized at the converter
  • Side fields on choice-less usage frames were dropped in JS, kept in Python
  • Repeated side fields discarded distinct payloads
  • A truncated response came back silently empty; the raw tag leaked non-streamed
  • Removed adminKey / admin_key — it reached _identifyingParams and persisted cache keys

Compatibility

  • @langchain/openai peer widened from an exact 1.5.5 to ^1.5.5 (an exact pin is an ERESOLVE against current latest)
  • use_responses_api=False pinned so a stray reasoning= kwarg cannot bypass every hook
  • 900 s default timeout, matching the core SDKs
  • Control options exposed: show_additional_info, bypass_cache, bypass_moa

Tests

  • JS unit 35 → 63; Python unit 28 → 47 at 99.10% coverage
  • Live QA suites in both languages, 29 and 30 checks, covering every modality plus the negative contract cases
  • ChatModelIntegrationTests declares real capability flags; three non-strict xfails for what Interfaze cannot support
  • pytest tests/integration_tests runs bare again; coverage moved to the CI command

Known, not fixed here

  • streamEvents() on the default protocol carries no response_metadata — core's message-finish has no slot for it
  • Structured output + inline <precontext> crashes. Root cause is server-side: interfaze/src/helpers/response.ts:706 emits <precontext> into content it knows is a JSON document, guarded only by showAdditionalInfo, while <think> and the other two precontext sites are correctly gated on !schema. One && !schema fixes it for every client SDK.

Blocked on a decision before publish

  • npm scope: package.json says @interfaze/langchain but that scope has no published packages; jsr.json says @interfaze-ai/langchain
  • Version drift: npm 1.0.0 / JSR 1.0.2 / PyPI 1.0.1 / version.ts 1.0.0
  • npm-publish and jsr-publish have no prerelease guard, so a prerelease tag ships real npm + JSR releases

🤖 Generated with Claude Code

Audited both packages against the interfaze npm/pip SDKs and the live
`/v1/chat/completions` contract. Ten defects, all verified against the API.

Both languages:
- Report interfaze, not openai. `_llm_type`, `ls_provider`, `lc_secrets`,
  `lc_namespace` and `response_metadata.model_provider` all said "openai", so
  every LangSmith trace was attributed to the wrong provider. Also stamp the
  package version into `lc_versions`.
- Drop the `precontext` constructor field. The server strips unknown top-level
  body keys and neither core SDK has such a param, so it never did anything —
  the README documented it as working.
- Reject `{type:"video", file_id}` client-side with a clear InterfazeError. The
  server's `file` part requires `file_data` and has no `file_id`, so it was a
  guaranteed 400.
- Infer the container mime type for video URLs, matching `inputs.video()`.
- Default the request timeout to 900s, matching the core SDKs; a single call may
  run OCR, a web search or a transcription inline.
- Expose the four control options (`show_additional_info` / `bypass_cache` /
  `bypass_moa` / `admin_key`). `show_additional_info` is the only way to get
  precontext while streaming.

JS only:
- Forward `reasoning_effort`. `@langchain/openai` only forwards it for model
  names matching its own reasoning heuristic (`/^o\d/`, `gpt-5*`), so
  interfaze-beta silently lost it and reasoning was unreachable from the package.
- Widen the `@langchain/openai` peer from an exact `1.5.5` to `^1.5.5`; the exact
  pin was an ERESOLVE for anyone on current latest.
- Reach `_streamChatModelEvents` via `BaseChatModel.prototype` instead of a
  double `getPrototypeOf` walk over an `@internal` class.

Python only:
- Stop leaking raw `<think>` text to token callbacks. `_iter_v2_events` is the
  one core path that passes `run_manager` into `_stream`, and ChatOpenAI fires
  `on_llm_new_token` before yielding, i.e. before the filter ran. `_stream` and
  `_astream` now withhold the manager and fire it themselves once the chunk is
  clean.
- Enable `stream_options.include_usage`. langchain-openai only auto-enables it
  for OpenAI's own base URL, so streamed responses carried no `usage_metadata`.
  JS already defaulted it on.
- Pin `use_responses_api=False` so a stray `reasoning=` kwarg or
  `LC_OUTPUT_VERSION` cannot reroute to /v1/responses and bypass every hook.

Tests: JS unit 35 -> 49, python unit 28 -> 36. New live suites mirroring the
interfaze-sdk-tests SPEC — 70 JS tests across 6 files (`npm run test:live`) and
72 python tests across 5 files — covering text, structured output, OCR, document
extraction and markdown, object/GUI detection, audio, video, translation, web
search, scraping, forecasting, reasoning, function calling, streaming, the code
sandbox, guardrails, `<task>` tags and the negative API-contract cases. All pass.
`ChatModelIntegrationTests` now declares real capability flags and xfails the two
cases the server cannot support (assistant list content, forced `tool_choice`).

Docs: drop the precontext section, document the control options, `<task>` and
`<guard>` via SystemMessage (which works — the docs said to use the core client),
a server-limits table, and how to run the live suites.
Comment thread js/src/chat_models.ts Outdated
}

override _llmType(): string {
return "interfaze-chat";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change to 'interfaze-beta'

@Abhinavexist

Abhinavexist commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Changes from my end

Bug Fixes

  • JS: Fixed reasoning_effort precedence to match upstream:
    options.reasoning.effortthis.reasoning.effortoptions.reasoningEffort → constructor value.
  • Python: Fixed streamed reasoning / precontext duplication by deduping side fields across merged chunks.
  • JS: Prevented empty final chunks when reasoning/precontext was already emitted.
  • Removed the unused JS streamEventProvider override.
  • Fixed audio input format in both live QA scripts.

Tests & QA

  • Replaced 13 unused live-test suites (~2,330 LOC) with two lightweight scripts:

    • python/scripts/qa_live.py
    • js/scripts/qa-live.ts
  • Added .github/workflows/qa-live.yml for manual + weekly live QA.

  • Split the Python unit tests into focused files; no tests were dropped.

  • Added coverage for reasoning precedence, stream deduplication, async token filtering, tail emission, and version sync.

  • Changed _llm_type to "interfaze-beta" across Python, JS, and tests.

Cleanup

  • Reworked JS/Python READMEs to match the root/SDK structure.
  • Trimmed unnecessary source comments.
  • Added .env / .env.* to .gitignore while preserving .env.example.

@Abhinavexist

Copy link
Copy Markdown
Collaborator

@Khurdhula-Harshavardhan

Issues fixed

Python

  1. reasoning={...} in the constructor crashed every call with TypeError — folded into reasoning_effort; the old test never invoked it, so it validated a broken config.
  2. Empty "" on the envelope overwrote and suppressed real inline reasoning — fixed at extraction via _carries_value(), covering streaming and non-streaming.
  3. vcache merged False + True → 1 (int, not bool), breaking isinstance checks including one in the live QA — deduped again.

JS

  1. Role-less deltas leaked raw reasoning and left model_provider: "openai"instanceof AIMessageChunk gate removed.
  2. Constructor reasoning.effort beat per-call reasoningEffort and .withConfig() — precedence reordered.

Both

  1. _llm_type settled on "interfaze".

- JS: side fields on choice-less usage frames now reach a chunk
- Both: dedupe side fields by value so distinct payloads are kept
- Both: vcache dedupes by name so True/False cannot merge to int 1
- JS: chunk() takes envelope fields; reverting the dedupe now fails a test
- pytest tests/integration_tests runs bare; coverage moved to the CI command
- Python QA: astream_events check, asserts reasoning is present first
- Both QA: assert <precontext> stays out of visible streamed text
- Both QA: reject multiple <task>, invalid task, empty message, bad base64
- Both QA: guardrails now S1-S14 plus a benign-passthrough assertion
- qa-live.yml: github.ref in concurrency group, uv sync --all-groups
- Non-strict xfail for the flaky test_bind_runnables_as_tools
… them

Two root causes behind the streaming patches, addressed at source:

- @langchain/openai picks the chunk class from `delta.role`, and only the
  assistant branch attaches additional_kwargs (so __raw_response). Role-less
  Interfaze deltas therefore became ChatMessageChunk with no side fields and
  isAIMessage() false. Normalize the role in the converter, as the core
  interfaze SDKs do, instead of compensating in three downstream places.
- Side fields on choice-less frames are now observed in completionWithRetry and
  drained onto the final chunk. Injecting a fabricated choice made the parent
  stamp usage on a second chunk, and _mergeDicts summed it, doubling
  response_metadata.usage.

- python: dedupe reads additional_kwargs too, so the response_format streaming
  branch no longer bypasses it and duplicates reasoning/precontext
- recover text when a <think> tag is left open by a truncated response;
  the filter buffered it and flush() discarded it, so the stream came back
  silently empty while the same body non-streamed leaked the raw tag
- emit only the swallowed remainder, not the whole text, so a tag opening
  mid-message no longer repeats the already-streamed prefix
- drop a half-written <precontext> instead of surfacing partial metadata
  JSON as message content
- remove adminKey / admin_key: an admin debug header does not belong on a
  chat model, and in JS it reached _identifyingParams and the persisted
  LLM cache key
@Khurdhula-Harshavardhan Khurdhula-Harshavardhan changed the title fix: correct provider identity, reasoning, and streaming before launch fix: launch readiness for @interfaze/langchain and langchain-interfaze Aug 8, 2026
- non-streaming no longer rewrites unmatched <think>/<precontext>. It has the
  whole body, so an unmatched tag is prose: "Wrap your reasoning in <think>
  tags" was coming back with the tag deleted, and a truncated closing tag
  ("</think") left raw markup behind. Recovery stays on the streaming path,
  which is the only one that actually swallows text.
- raise the `interfaze` floor to >=1.0.3: 1.0.2 sends x-bypass-moe and
  x-bypass-cache, which the server does not read (it reads
  x-interfaze-bypass-moa / x-interfaze-bypass-cache)
The SDK builds its final completion by running strip_side_channels over the
whole transcript, so an unmatched <think> survives verbatim — it only strips
complete pairs. Our tail chunk now reproduces that: the SDK's full text minus
what already streamed.

Fixes two divergences, both introduced by the earlier ad-hoc recovery:
- "Wrap your reasoning in <think> tags" streamed back with the tag deleted
- a response truncated mid-<think> dropped the tag and presented the model's
  chain-of-thought as the answer

Verified against strip_side_channels for complete, truncated, mentioned and
split-across-chunks tags: all four now match the SDK byte for byte.
@Khurdhula-Harshavardhan
Khurdhula-Harshavardhan force-pushed the fix/launch-blockers-and-live-test-suites branch from 8106aad to 0782076 Compare August 8, 2026 19:37
Khurdhula-Harshavardhan and others added 13 commits August 8, 2026 12:39
Read both packages end to end; the four remaining defects were all in how the
final stream chunk and the non-streaming result assemble side fields.

- envelope frames are folded into `seen` before the inline values are judged,
  and merged last, so an envelope `reasoning` is no longer replaced by the
  inline one (previously only "INLINE" survived)
- folding several choice-less frames now appends instead of overwriting, so
  two precontext entries both survive instead of only the last
- `generation.text` follows the stripped content on the non-streaming path,
  as the streaming path already did; raw <think> was reaching callbacks,
  traces and the serialized LLM cache
- an empty `precontext: []` is a real answer ("no tools ran") and stays on
  response_metadata; the READMEs document bracket access, which was raising

Six unit tests added across the two packages, one per behaviour.
The tail had grown five interacting parts (filter, fingerprint dedupe, frame
sink, leftover fold, recovery) and was the source of most recent defects.
Replaced the fold with one chunk per side-field source, so langchain's own
merge concatenates them — the behaviour the python package already got for
free from per-chunk conversion.

Removed:
- the `accumulate` mode on applySideFields / _apply_side_fields; it was dead
  code in python (never called with three args) and unnecessary in JS once
  each source emits its own chunk
- the leftover / hasLeftover bookkeeping and the Object.assign merge

Fixed, both introduced by the previous commit:
- an envelope `precontext: []` no longer blocks the real inline payload; JS
  used an `=== undefined` guard where python used truthiness
- tail recovery no longer returns nothing when the visible text starts with
  whitespace. strip_side_channels trims and streamed text does not, so the
  prefix compare failed on the common `</think>\n` shape; both languages now
  diff against an untrimmed strip

Also aligned the JS side-field emission to wire order, so inline and envelope
reasoning concatenate the same way in both packages.
- buildHeaders normalizes `configuration.defaultHeaders` instead of
  object-spreading it. It is typed HeadersLike, so a Headers instance spread to
  {} and a tuple array to {"0": [k, v]} — silently dropping a caller's
  auth/tenant header whenever a control flag was also set. Verified across all
  three shapes.
- python `_identifying_params` includes the control headers, so the LLM cache
  key distinguishes a bypass_cache model from a plain one. With set_llm_cache
  on, the second was being served the first's answer and never reached the API.
- export InterfazeReasoningEffort; it is the declared type of a public
  constructor field and callers had no way to name it.
- drop `classification` from the task lists in all three READMEs; it is not in
  the SDK's TASK_NAMES and returns a 400.
- one version everywhere (1.0.0). npm/JSR/PyPI/src had four different numbers
  and nothing is published yet.
- npm package renamed to @interfaze-ai/langchain to match the JSR scope, which
  is the scope that demonstrably exists.
- npm and JSR publish jobs skip prereleases, matching the python jobs; a
  prerelease tag was shipping real releases to both.
- buildHeaders normalizes configuration.defaultHeaders. This was written and
  verified earlier, then wiped when I reverted an unrelated failed experiment
  in the same file with `git checkout --`, so bc23248 shipped the claim without
  the code. Three parameterized tests now pin the plain-object, Headers and
  tuple shapes so it cannot silently disappear again.
- restore `classification` to the task lists. It is in the server's allowlist
  (utils/messaging.ts), so it works today; the SDK's TASK_NAMES omitting it is
  a separate gap and was not evidence of a 400.
- correct the role-normalization comment: Interfaze does send `role` on the
  first delta, verified on the wire. The override is defensive, matching what
  interfaze-python's own stream accumulator does, not a workaround for
  observed behaviour.
Core behaviour, not workarounds:

- a half-written <precontext> is never rendered as content. `visibleText` /
  `_visible_text` now cut at an unterminated <precontext>, so a stream truncated
  mid-blob can no longer surface OCR'd text (SSNs, card numbers) as the answer.
  <think> still survives verbatim, matching the SDK — it is prose, not metadata.
- reasoning precedence is one ladder in both packages: call `reasoning.effort`,
  then call `reasoning_effort`, then the model's. Python previously let a
  model-level value beat a per-call one.
- control headers are matched case-insensitively. `X-Interfaze-Bypass-Cache:
  false` plus ours was reaching the wire as "false, true"; JS gets this from
  normalizeHeaders, python lower-cases incoming keys.
- the python LLM cache key keys on header values, not just names, so two
  tenants no longer share a cache entry.
- video blocks treat an explicit `url: null` / `file_id: null` as absent, as
  python already did; JS was throwing a raw TypeError out of videoMimeFromUrl.
- synthetic tail and side-field chunks carry the stream's id, so consumers that
  group by message id stop seeing orphan messages.
- npm and JSR publish jobs depend on the python build, so a failed build cannot
  ship half a release; the interfaze peer is bounded <2 like python's.
- the conformance xfail override takes real fixtures instead of *args, which
  made it TypeError and report a vacuous pass.
- unit tests run with --disable-socket in CI, and the two video guard tests are
  mocked, so a regression cannot silently POST a live key from every runner.
- JS dedupes side fields with a key-order-stable serialization, matching
  python's json.dumps(sort_keys=True). The same payload delivered with
  reordered keys was deduping in python and double-emitting in JS.
- `_generate` uses isAIMessage, not `instanceof AIMessage`. AIMessageChunk is
  not an AIMessage, so the whole post-processing block — side fields, tag
  stripping, the generation.text sync — was dead under `streaming: true`.
- the frame sink is released when the stream ends, instead of being retained
  for the lifetime of the options object.
- mypy covers python/scripts, which the JS twin already had under tsc. It
  immediately found a real `func-returns-value` error in the live QA script
  that would have surfaced as a Monday crash.
- the version-sync test reads jsr.json too, and asserts name as well as
  version. jsr.json had silently drifted to 1.0.2 under a test that only ever
  looked at package.json.
- an empty-stream `providers.every(...)` assertion passed vacuously; it now
  compares the observed set.
…he keys

An unmatched <think>/<precontext> means two different things. In a completed
response it is prose the model wrote; in a truncated one it is a side channel
the server never closed. finish_reason == "length" is the only signal that
tells them apart, so both packages branch on it: prose survives verbatim, a
truncated <think> becomes reasoning metadata, and a truncated <precontext> —
unparseable tool JSON — is dropped rather than leaked as content.

python: with_structured_output streams through beta.chat.completions, which
nests each frame under "chunk" and omits `role` on the completion it
assembles. Side fields were read from the wrong level and the missing role
raised ValidationError. Normalize the role on both shapes and unwrap the
envelope, matching what the js package already did.

js: envelope side fields were buffered to stream end, so they arrived out of
order and vanished if the consumer broke early. Apply them to the chunk they
arrived with instead.

both: _identifying_params reaches the llm cache key and the invocation_params
langsmith records, so the api key and header values were published verbatim.
Fingerprint them — two values still differ, which is all the key needs.
file_id is rejected on any block, not just video, since interfaze has no file
store; scalar header values are stringified rather than silently dropped.
Five files carry the version and two of them ship as a User-Agent, so a
release cut from the wrong commit would publish the previous version to three
registries without complaint. A single check compares all five — to each other
on every PR, and to the release tag before anything is built or pushed.

The publish jobs also ran neither test suite; a release from any commit could
ship code that never passed. They now depend on a verify job that does.
twine check warned that long_description was missing: pyproject never pointed
at README.md, so the published project page would have rendered empty.
…aration

_streamChatModelEvents delegated to BaseChatModel, which buys tag-stripping and
loses everything else: convertChunksToEvents hardcodes reason: "stop" and emits
no responseMetadata, so a v3 consumer saw "stop" on a truncated response and had
no way to reach precontext, reasoning or vcache. Neither inherited implementation
is usable as-is — the parent's reads the raw stream and leaks <think> — so
convert our own stripped chunks and put the metadata back on the terminal event.
Both live gates now assert the finish reason; neither did, which is why this
went unnoticed.

A closed but empty <think></think> sets reasoning to "": present, so `??` kept it
over the recovered tail, while python's `or` fell through. Truncated input
"<think></think>visible<think>partial" lost its reasoning in js only.

python's cache key had nothing derived from the api key, so two tenants shared
cache entries — the previous commit's claim that both packages fingerprint the
key was true of js alone. file_id is now rejected in the openai-native
{file: {file_id}} nesting too, and js emits a standalone chunk for an envelope
frame exactly where python does.

Also: guard `process` so a browser bundle throws InterfazeError rather than
ReferenceError, raise the @langchain/core peer floor to ^1.2.5 to match what
@langchain/openai itself requires, drop the langchain-openai <1.5 ceiling that
would block installs the day 1.5.0 ships, and bound the QA request timeout so a
hung call fails its own check instead of the whole job.
…coverage

The #11 gate assertion left `end` as Any | None, which mypy rejects because
_assert is a function and does not narrow the way a bare assert would — the
py3.12 CI job was red.

Two more of the same `??`-vs-`or` split the earlier pass missed. An empty-string
mime_type is present-but-empty, so js shipped `data:;base64,…` — text/plain —
where python shipped video/mp4. And _streamChatModelEvents accumulated metadata
with Object.assign, which replaces: precontext is emitted one chunk per source
precisely so langchain's merge concatenates it, so streamEvents showed the last
source where .stream() showed both. It now accumulates through the same concat
its consumers use.

The api key fingerprint is `interfazeKey` to pair with python's `interfaze_key`.

The api server gained docx support (interfaze 0a967da); it converts to PDF at
ingestion, so nothing here needed changing, but both live gates now cover it and
the readmes say so.
Accumulating the terminal v3 metadata with concat fixed precontext but broke
usage: the parent puts the same `usage` dict on two chunks and langchain's merge
adds numbers, so message-finish reported exactly double the tokens a request
cost. Every other openai-compatible provider reports true usage there, and
consumers read it for cost attribution.

Last wins for anything the server restates, concat only for the two fields
emitted one chunk per source precisely so that langchain concatenates them. That
split is the one the package already documents, so system_fingerprint and
service_tier are covered too if interfaze ever sends them.
… looked for

Review follow-ups on 7ade8af. Only the first is behavioural.

- `_open_side_channel` / `openSideChannel` scanned for `<think>` before
  `<precontext>` and returned whichever it found first in *that* order, not the
  one that appears first in the text. Tool JSON can quote the string `<think>`
  (a scrape of a page that discusses it, say), so a truncated response carrying
  an unclosed `<precontext>` around such a payload split at the quoted tag: the
  raw `<precontext>` and the tool output leaked into the answer, and the rest
  became `reasoning`. Both packages now take the earliest tag by position. Each
  new test was confirmed to fail against the previous scan.
- `_redact_headers` inlined `hashlib.sha256(...)[:12]` instead of calling the
  `_digest` helper declared directly above it, so the two would drift apart the
  first time either changed.
- Correct the `digest` comment in the js package: the value does travel to
  LangSmith, so "never compared across processes" was the wrong justification.
  The real one is that FNV-1a is not a security boundary here — 32 bits cannot
  be reversed to a key, and distinctness is all the cache key needs.
- `stripSideChannels(joined)` ran twice in `_streamResponseChunks`.
- Turn pytest warnings into errors, with a narrow ignore for the pydantic
  serializer warning `ParsedChatCompletion` raises upstream. A new warning is
  now a failure rather than a line nobody reads.

Add CONTRIBUTING.md. The `tests/integration_tests` runbook had no home: CI runs
only `tests/unit_tests/`, so the langchain-tests conformance suite was
undiscoverable, and a pyproject comment kept getting stripped. It also documents
the live QA gate and the five-file version check.

Unit: python 74 -> 75, js 95 -> 96, green on py3.10-3.13. Live QA 31/31 python,
30/30 js.
@Abhinavexist
Abhinavexist force-pushed the fix/launch-blockers-and-live-test-suites branch from 6a1cc74 to b4503a2 Compare August 10, 2026 08:19
Comment-only pass over both packages. Verified no code changed: the python
files parse to an identical AST with docstrings removed, and the ts files hash
identically with comments stripped.

Removed section banners (`# defaults`, `# core`, `// input channels`), a
`_first_effort` docstring that restated the comment at its only call site, a
provenance citation quoting interfaze-python's implementation line, and roughly
half the length of the longer blocks — `_default_role`, `_redact_headers`,
`_recover_tail`, `_streamChatModelEvents`, the role-normalization note and the
FNV digest note all keep every load-bearing clause in one or two sentences.

Corrected `hasValue`, whose doc claimed an empty array counts as "present with
no entries" when the function returns false for one. It now records why the
helper exists: `[]` is truthy in JS, so a bare Boolean() would let an empty
precontext block the real one.

Added three comments where the reasoning was not inferable:

- `run_manager=None` in `_stream`/`_astream`. Passing None to super() reads as a
  bug; "fixing" it puts unfiltered `<think>` back in front of token callbacks.
  This had been written before and stripped again.
- A short `__init__` docstring for the three control flags. That
  `show_additional_info` is the only way to get precontext while streaming does
  not follow from `bool`.
- `showAdditionalInfo` on ChatInterfazeFields, whose two siblings were already
  documented.

Kept everything describing upstream behaviour or a cross-package invariant: the
HeadersLike spreading trap, normalizeHeaders dropping non-strings, the parent
discarding choice-less frames, the WeakMap-keyed-on-options concurrency
guarantee, gen.text needing manual sync, last-wins metadata avoiding
double-counted usage, and stableStringify matching python's sort_keys=True.
Readability pass over every source, test and config file. No behaviour change:
`dist/index.d.ts` is byte-identical to HEAD, and the python public surface
(`__all__`, constructor signature, class-defined members) is unchanged.

`_stream` and `_astream` threaded four mutable accumulators through free
functions — `_final_side_chunk(filt, raw, seen, emitted, finish == "length")` —
and repeated five lines of identical setup. That state is now
`_SideChannelStream`, so the two loops differ only by `async`:

    stream = _SideChannelStream()
    for gen in super()._stream(...):
        stream.absorb(gen)
        ...
    final = stream.final_chunk()

`absorb` keeps the filter -> dedupe -> finish-reason order, which is
load-bearing and was previously re-established in both loops.

`_open_side_channel` returned a bare 3-tuple read as `open_tag[0]`,
`open_tag[1]`, `open_tag[2]`, and `_recover_tail` a 2-tuple unpacked under two
different names. Both are NamedTuples now, which also matches the objects the js
side already returned.

side_channels.ts: `lt` (an index of `<`, reading like a comparison) ->
`openAngleAt`; `suffixPrefixLen` -> `danglingTagPrefixLength` with a line saying
it holds back a tag split across chunks; `#buf`, `pre`, `thinks` spelled out.

Smaller: `finishReason: unknown` -> `string | undefined`; `#frameSinks` moved
from mid-class to the field block; one cast of `gen.message` instead of two on
adjacent lines; `hasValue`'s comment corrected, having claimed an empty array
counts as present when the function returns false for one.

Tests: two hand-rolled chunk-merge folds -> `merge()` in conftest;
stream_events.test.ts hand-rolled a frame `envelopeChunk()` already produces;
`test_all_imports` -> `test_public_exports_are_pinned`.

CONTRIBUTING said the integration suite needs `--no-cov` "because it has its own
coverage expectations". `addopts` is `""`, so nothing adds coverage and the flag
is a no-op.
…untested

Two gaps in the live gate.

`streaming (tags stripped)` asserted no `<think>`/`<precontext>` in the output
of "Count 1 to 5." — no reasoning_effort, no attachment, so that response can
never carry a tag. The assertion was structurally incapable of failing. It stayed
green through the role-less-delta bug fixed in 8c27113, where `<think>` streamed
straight to callers, because it never asked the server for reasoning. The same
defect was found and fixed in the streamEvents check and not carried across to
the one beside it. Both languages now stream at reasoning_effort high through the
cache-bypassing client and assert reasoning surfaced before asserting nothing
leaked.

Nothing exercised the MoA router. Every precontext check either uploaded a
receipt or forced the tool with a `<task>` tag, so the one behaviour that
separates Interfaze from any OpenAI-compatible endpoint had no coverage — while
all three READMEs lead with `invoke("Which US public companies reported earnings
today?")` and the claim that a web search backs the answer. That claim is now a
check, and it holds: precontext comes back naming `search`.

Also in these files: five near-identical `rejects_*` functions collapsed onto the
`rejects(name, detail, run)` helper the js side already had, and the js
temperature case folded into it (the file_id case stays separate — it asserts a
client-side InterfazeError, not a 400). `A` -> `ASSETS` to match js. A local
`fresh` in `_astream_events` shadowed the module-level `fresh`. The repeated
`from interfaze import BadRequestError` inside four function bodies hoisted to
the module level, where InterfazeError already was.

Live: python 32/32, js 31/31.
@Abhinavexist

Copy link
Copy Markdown
Collaborator

@Khurdhula-Harshavardhan the pr now LGTM

npm and jsr become @interfaze/langchain, and the python distribution becomes
interfaze-langchain with the module renamed to interfaze_langchain to match.
PyPI has no scoping, so the scope/name order is the closest equivalent there.

This diverges from the langchain-<provider> convention the langchain docs ask
for, and from what the other provider packages on npm do (@langfuse/langchain,
@composio/langchain, @sap-ai-sdk/langchain all keep their own scope but the
python side stays langchain-*). Deliberate product decision, recorded here so
nobody 'fixes' it later.

The github repo keeps its name, so the repository urls still point at
InterfazeAI/langchain-interfaze — npm provenance verifies that url against the
building repo and would fail on a mismatch.
Regenerating package-lock.json on darwin recorded only the darwin rollup binary,
so `npm ci` on linux failed with "Cannot find module @rollup/rollup-linux-x64-gnu"
and node 24 additionally reported the lock out of sync with package.json. All
four js jobs were red; nothing was wrong with the code.

Taking the lockfile as it was and editing only the two name fields keeps every
optional platform entry. Diff against the pre-rename lock is two lines.
filterwarnings gated ci on the message string of a warning langchain-openai
provokes in pydantic. A reword upstream turns that into a red build here, and
uv.lock is gitignored so ci re-resolves pydantic fresh on every run. Matching
the module keeps the intent without the coupling.
@Khurdhula-Harshavardhan
Khurdhula-Harshavardhan merged commit 8c8289d into main Aug 10, 2026
10 checks passed
@Abhinavexist
Abhinavexist deleted the fix/launch-blockers-and-live-test-suites branch August 11, 2026 02:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants