fix: launch readiness for @interfaze/langchain and langchain-interfaze - #2
Merged
Khurdhula-Harshavardhan merged 28 commits intoAug 10, 2026
Merged
Conversation
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.
Abhinavexist
reviewed
Aug 7, 2026
| } | ||
|
|
||
| override _llmType(): string { | ||
| return "interfaze-chat"; |
Collaborator
There was a problem hiding this comment.
change to 'interfaze-beta'
Collaborator
|
Changes from my end Bug Fixes
Tests & QA
Cleanup
|
Collaborator
|
Issues fixed Python
JS
Both
|
- 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
- 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
force-pushed
the
fix/launch-blockers-and-live-test-suites
branch
from
August 8, 2026 19:37
8106aad to
0782076
Compare
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
force-pushed
the
fix/launch-blockers-and-live-test-suites
branch
from
August 10, 2026 08:19
6a1cc74 to
b4503a2
Compare
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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Launch-readiness pass over both packages, audited against the
interfazenpm/pip SDKs and the live/v1/chat/completionscontract. Every fix was reproduced against the real API before and after.Correctness
reasoningEffortnever reached the wire in JS —@langchain/openaidrops it for models outside its own heuristic, so reasoning was unreachable despite being documentedprecontextconstructor 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 errorformat, unlikeinputs.video()_llm_type,ls_provider,lc_secrets,lc_namespace,model_provider<think>to token callbacks on the v2-protocol pathusage_metadata; JS already defaulted it onreasoning={...}crashed every request with aTypeErrorvcachemerged toint 1when it flipped mid-stream<think>and failedisAIMessage()— now normalized at the converteradminKey/admin_key— it reached_identifyingParamsand persisted cache keysCompatibility
@langchain/openaipeer widened from an exact1.5.5to^1.5.5(an exact pin is an ERESOLVE against current latest)use_responses_api=Falsepinned so a strayreasoning=kwarg cannot bypass every hookshow_additional_info,bypass_cache,bypass_moaTests
ChatModelIntegrationTestsdeclares real capability flags; three non-strict xfails for what Interfaze cannot supportpytest tests/integration_testsruns bare again; coverage moved to the CI commandKnown, not fixed here
streamEvents()on the default protocol carries noresponse_metadata— core'smessage-finishhas no slot for it<precontext>crashes. Root cause is server-side:interfaze/src/helpers/response.ts:706emits<precontext>into content it knows is a JSON document, guarded only byshowAdditionalInfo, while<think>and the other two precontext sites are correctly gated on!schema. One&& !schemafixes it for every client SDK.Blocked on a decision before publish
package.jsonsays@interfaze/langchainbut that scope has no published packages;jsr.jsonsays@interfaze-ai/langchainversion.ts1.0.0npm-publishandjsr-publishhave no prerelease guard, so a prerelease tag ships real npm + JSR releases🤖 Generated with Claude Code