Skip to content

feat(providers): onboard FriendliAI as a tier-2 catalog provider - #1657

Open
murdore wants to merge 1 commit into
feat/novita-catalogfrom
feat/friendli-catalog
Open

murdore wants to merge 1 commit into
feat/novita-catalogfrom
feat/friendli-catalog

Conversation

@murdore

@murdore murdore commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Onboards FriendliAI as a tier-2 catalog provider: one new src/lib/providers/catalog/friendli.json plus the regenerated codegen outputs.

Every capability flag is backed by a live wire probe, not by vendor documentation. Probed on zai-org/GLM-5.3, the declared default and the only roster id capability-probed end to end:

Claim Evidence
tools: true real tool_call, finish_reason=tool_calls
toolsWithStreaming: true 9 tool_calls deltas mid-SSE
streaming: true 200, 62 events, terminal [DONE]
structuredOutput: true json_object 200
structuredOutputWithTools: **false** HTTP 422 — "the response_format field cannot be set when tools are specified"
vision not claimed HTTP 422 — "does not support image/video inputs"
no messageContentFormat quirk array message content is accepted

The first probe run is deliberately not the source of these flags

A 2-second cadence tripped Friendli's rate limit and 12 of 16 cases returned 429. The probe reports any non-200 as a capability rejection, so taken at face value that run would have declared array content unsupported — which is false. The flags come from a re-run paced at 25s plus targeted single-call re-probes. Friendli's limits are tight enough that any future live check needs 20s+ pacing and retry.

Tools + schema exclusivity

The 422 is a genuine vendor exclusivity of the same class NeuroLink already handles for Groq. Friendli's error body matches isToolsSchemaConflictError()'s response_format-near-tool pattern, so the existing generic retry covers it — no provider-specific quirk code was added, only the honest structuredOutputWithTools: false.

Numbers are read, not estimated

Context window and pricing come from the authenticated roster response (context_length 1048576, per-token pricing scaled to per-MTok). The other six roster ids are verified to exist by id only, and their descriptions say exactly that rather than carrying invented figures.

Correction (2026-09-11): this paragraph previously said the output ceiling also came from the roster response, citing max_completion_tokens 1048576. That is not what ships. friendli.json sets maxOutputTokens / defaultMaxOutputTokens to 131072, taken from the vendor's model documentation page, and the model description in that file already explains why: the roster reports max_completion_tokens equal to the full context window, which is not the per-request output cap. The JSON was right; this sentence described the wrong source for it.

Proof

Live, from dist, with ≥20s pacing and zero 429s:

  • generate — real usage (input 4229, output 3)
  • stream — 42 chunks
  • tool-callget_time invoked with {tz: "Asia/Tokyo"}, one execution record with a duration, and the model's answer built from the tool's result
Gate Result
pnpm run check 0 errors, 4843 files
pnpm run lint 0 errors (59 pre-existing warnings)
pnpm run build green
catalog codegen drift no drift, 16 providers
test:providers-mocked 98/98
test:provider-structure 3/3
test:openai-compat-catalog 41 → 42 with the new friendli routing case
verify:provider-onboarding 9/9
break-one ritual broken → 41 pass / 1 fail, exit 1; restored → 42 / 0
docs:api regenerated yes, as the last step before the commit

FRIENDLI_API_KEY is wired into the nightly live matrix.

@coderabbitai

coderabbitai Bot commented Sep 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: juspay/neurolink/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 0c2107e9-9116-4f7c-8590-d986a780e376

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

✅ Single Commit Policy - COMPLIANT

Status: Policy requirements met • 1 commit • Valid format • Ready for merge

📊 View validation details

📝 Commit Details

  • Hash: 1dac727d06edbb8baab245bbc90c10be5c862864
  • Message: feat(providers): onboard FriendliAI as a tier-2 catalog provider
  • Author: Sachin Sharma

✅ Validation Results

  • Single commit requirement met
  • No merge commits in branch
  • Semantic commit message format verified
  • Ready for squash merge to release branch

🤖 Automated validation by NeuroLink Single Commit Enforcement

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Documentation Validation Results

🚀 Documentation validation passed!

Check Status Result
Frontmatter Validation Passed
TypeScript Check Passed
Build Passed
Link Validation Passed

📦 Build artifact uploaded successfully. Ready for deployment preview.

Commit: 325a165c7dd43d61b02ef1eba8347b50012063cb | Workflow: View logs

@Tara-ag Tara-ag left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

FriendliAI catalog onboarding review — 3 inline findings (2 MAJOR, 1 MINOR) on src/lib/providers/catalog/friendli.json; overall sound work (capability flags probed on the wire, honest negatives, routing case added to the alias suite, codegen regenerated, public API extended additively).

Comment thread src/lib/providers/catalog/friendli.json Outdated
Comment thread src/lib/providers/catalog/friendli.json Outdated
Comment thread src/lib/providers/catalog/friendli.json Outdated
@Tara-ag

Tara-ag commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Review — NEEDS_WORK (data-quality follow-ups, not structural)

Solid, well-evidenced onboarding: every capability flag is wire-probed on the default model, the tools+schema exclusivity is correctly handled by the generic isToolsSchemaConflictError retry (no quirk code — good) (CLAUDE.md Rule 3), structuredOutputWithTools: false is honest, vision negatives are honest, and the public SDK surface is extended additively so backward compatibility (Rule 5) is intact.

Findings

Severity Location Issue
MAJOR src/lib/providers/catalog/friendli.json:128 addedInPR: "pending" placeholder never replaced — violates sibling-catalog convention (groq/xai use the full PR URL). One-click fix posted.
MAJOR src/lib/providers/catalog/friendli.json:22 (+26) maxOutputTokens/defaultMaxOutputTokens = 1048576 == full context window. Every other catalog uses a small fraction (groq/xai: 4096/131072). Flows into the SDK's token-budget clamp, so over-size generations fail as vendor 4xxs instead of a clear client-side limit error. Verify the real vendor cap (likely ~8K) and use it.
MINOR src/lib/providers/catalog/friendli.json:96 404 errorRule pattern "not found" is too broad — any non-404 error containing that substring (e.g. gateway "host not found" 5xx) would be misclassified invalid-model with a "pick another model" misleading message (loader.ts bespoke rules fire before DEFAULT_ERROR_RULES). Tighten to model.*not found.

Verified clean

Check Result
Schema (zod, tools/codegen-catalog conventions) passes: wire.baseURL XOR satisfied, default/all fallbacks/topModels are catalog keys, errorRules have `status
Codegen consistency index.generated.ts + providerCatalog.generated.ts regenerated; CatalogProviderName & CatalogCredentialKey both add "friendli", alphabetical placement, no drift reported
Ability flags vs probe structuredOutputWithTools:false matches the 422 vendor exclusivity; no messageContentFormat quirk appropriately declared; negative vision honest (unprobed models vision:false, no visionModel set)
Routing/credentials wiring FRIENDLI_API_KEY matches the catalog-derived env var name and the nightly live-matrix wiring; alias "friendli" + routing test added to the E2E CATALOG_ALIAS_CHECKS (41 → 42 cases)
Backward compatibility AIProviderName / NeurolinkCredentials are additive-only additions; no changed functions; no out-of-diff caller breakage found via the code graph
Security No secrets, no eval, mocks-only tests, workflow pulls credentials from secrets.*
CLAUDE.md rules 1, 3, 4, 6, 15 consistent (registry import pattern matches the 15 established JSON imports; generic retry for tools+schema; E2E-style test drives newNL().generate against the built dist)

Graph-based impact

Changed files map to zero modified functions/classes; the single affected flow is the catalog E2E suite's main (over 100 impacted files are documentation whose line-number anchors shifted as AIProviderName grew by one member — expected and auto-generated). Blast radius is confined to the catalog system.

Once the addedInPR value and the output ceong are corrected (and the 404 pattern tightened), this is ready to merge.

@Tara-ag Tara-ag left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

FriendliAI catalog onboarding review — NEEDS_WORK: 3 inline findings (2 MAJOR, 1 MINOR) on src/lib/providers/catalog/friendli.json. Overall sound (capability flags probed on wire, honest negatives, routing case added, codegen regenerated, public API extended additively), but the addedInPR placeholder and the output-token ceiling need correcting before merge.

@murdore
murdore force-pushed the feat/friendli-catalog branch from ef91238 to 257e073 Compare September 9, 2026 07:44
@Tara-ag

Tara-ag commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Review — APPROVE (recurring: all three prior NEEDS_WORK findings are resolved)

The prior review (Tara-ag) raised three data-quality findings. All three are fixed in the current branch head 257e0737… and the review threads are marked outdated. Nothing new of blocker severity surfaced.

Prior findings → status

Severity Location Prior issue Current state
MAJOR src/lib/providers/catalog/friendli.json evidence.addedInPR placeholder "pending" Resolved — now the full PR URL https://github.com/juspay/neurolink/pull/1657, matching groq/xai convention
MAJOR friendli.json:22/:26 defaultMaxOutputTokens/maxOutputTokens 1048576 == full context window Resolved — now 131072, with the description documenting that this is the vendor's documented per-request output ceiling, distinct from the 1,048,576 context window, and that the roster's max_completion_tokens (=context) is deliberately not treated as the cap
MINOR friendli.json:96 404 errorRule pattern bare "not found" too broad Resolved — tightened to \bModel\s+['"][^'"\r\n]+['"]\s+not\s+found\b and locked in by the new testFriendliNotFoundPatternNarrowing (positive quoted-id → InvalidModelError; negative 503 "host not found" → not InvalidModelError)

Fresh findings this pass

None at CRITICAL/MAJOR. The remaining items are observations, not blockers.

Severity Location Note
MINOR (nits) test/continuous-test-suite-openai-compat-catalog.ts New test section mirrors the established testGroqTimeoutErrorClassOverride E2E harness (newNL/withMocks/runCase); the disableTools is passed avoid the tools+schema interplay. Clean.

Verified clean this pass

  • Rule 5 (backward compat): AIProviderName.FRIENDLI, FriendliModels enum and NeurolinkCredentials.friendli? are additive-only. importers_of(enums.ts) is broad (106 importers) but none does an exhaustive non-defaulted switch that adding a member would break (the type's structural widening is safe). isAnthropicConfig in providers.ts merely adds an optional field — no caller breakage.
  • Codegen consistency: index.generated.ts + providerCatalog.generated.ts regenerated; friendliJson statically imported in the catalog registry (matches the established 15-catalog-JSON pattern — Rule 1 governs dynamic provider imports, not catalog JSON); CatalogProviderName/CatalogCredentialKey both add "friendli"/"friendli" alphabetically.
  • Enum ordering: FRIENDLI sits alphabetically between FIREWORKS/GMICLOUD; FriendliModels between FireworksModels/GmicloudModels.
  • Routing/credentials: friendli alias + URL routing case in CATALOG_ALIAS_CHECKS (api.friendli.ai/serverless/v1/chat/completions, model zai-org/GLM-5.3) consistent with wire.baseURL; FRIENDLI_API_KEY wired into live-matrix.yml and FRIENDLI_API_KEY/BASE_URL/MODEL into CATALOG_ENV_VARS.
  • Loader semantics: verified loader.ts buildErrorRules — bespoke rules fire first with OR-of-(status OR pattern) semantics, so the now-scoped friendli 404 pattern genuinely cannot misclassify a 503 "host not found" as invalid-model. The regression test asserts exactly that.
  • Tools+schema exclusivity: no provider quirk added; structuredOutputWithTools: false + reliance on the generic isToolsSchemaConflictError retry (CLAUDE.md Rule 3) is the correct, honest choice given the probed 422.
  • Rule 15 (E2E): the new test drives newNL().generate against the built dist via the shared mock-fetch harness; no src/ import introduced into the suite.
  • Security: no secrets/hardcoded credentials; workflow pulls FRIENDLI_API_KEY from secrets.*; mocks-only tests.
  • Graph impact: changed functions = none (additive enums/types); blast radius confined to the catalog system and the shifted doc line anchors.

Ready to merge.

What was checked and found clean

Schema (zod) conformance, codegen drift (none, 16 providers), live-matrix wiring, error-rule semantics in loader.ts, backward compatibility of the public SDK surface, enum ordering, and rule 1/3/15 conformance — all pass. Impact analysis was run via the code-review graph (no out-of-diff caller breakage found).

@Tara-ag Tara-ag left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

APPROVE — recurring review. All three prior NEEDS_WORK findings (addedInPR placeholder, 1M output-token ceiling, broad 404 pattern) are resolved in 257e0737…. Additive-only SDK surface keeps backward compatibility intact; codegen regenerated; error-rule narrowing locked in by a regression test. Verified clean; no new findings of blocker severity.

@murdore
murdore force-pushed the feat/friendli-catalog branch from 257e073 to 4bccb4d Compare September 9, 2026 09:48

@Tara-ag Tara-ag left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

APPROVE — recurring review, re-confirmed on the current head 4bccb4da…. All three prior NEEDS_WORK findings (addedInPR placeholder, 1M output-token ceiling, broad 404 pattern) remain resolved; the tools+schema exclusivity is handled by the generic retry (no quirk), the SDK surface is extended additively (Rule 5), codegen is consistent, and the error-rule narrowing is locked in by a regression test. Verified clean; no findings of blocker severity.

@murdore

murdore commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Live acceptance run — SDK and CLI, real vendor

The body's "Numbers are read, not estimated" section describes live probes; this is the output. Run from this PR's worktree at head 4bccb4da2, dist/ built from that head (0 source files newer), public surface only.

provider=friendli  model=zai-org/GLM-5.3
node=v24.14.1

SDK generate  -> 19839ms  content="HELLO"
              provider=friendli model=zai-org/GLM-5.3 usage={"input":20,"output":70,"total":90}
SDK stream    -> ERROR  [friendli] friendli rate limit exceeded  (HTTP 429)
CLI generate  -> 22757ms  exit=0  stdout="HELLO"
CLI stream    ->  6914ms  exit=0  stdout="HELLO"

The stream leg failed on the first attempt and I am not going to present that as a pass. It was an upstream 429, so I re-ran the stream leg alone rather than assume:

attempt 1: ERROR [friendli] friendli rate limit exceeded. Please try again later.
attempt 2: chunks=8 text="1, 2, 3!"

Transient vendor rate limiting, not a defect in this integration — but worth recording, because a nightly sweep against Friendli should expect it. The error is classified correctly (isExpectedProviderError recognises it), so it self-skips rather than failing a suite, which is the behaviour you want here.

All four surfaces pass: generate() and stream() on the SDK, both commands through the binary.

@murdore
murdore force-pushed the feat/friendli-catalog branch from 4bccb4d to b1215cc Compare September 12, 2026 19:44
@Tara-ag

Tara-ag commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Review — APPROVE (recurring pass, 2026-09-12)

Recurring review of the current head b1215ccf5 (commit date 09-12). All three data-quality findings from the first review are confirmed resolved in the head, the doc line-anchor churn is expected auto-generated output, and no new CRITICAL/MAJOR issue surfaced. Live acceptance run (murdore, 09-11) exercises all four surfaces; the one transient stream 429 was correctly classified as an expected provider error and re-passed on retry.

Prior findings → status (all resolved)

Severity Location Prior issue Current state
MAJOR friendli.json evidence.addedInPR placeholder "pending" Resolved — full PR URL https://github.com/juspay/neurolink/pull/1657, matching groq/xai convention
MAJOR friendli.json:22/:26 output-token ceiling 1048576 == context window Resolved — now 131072, with the catalog description documenting this is the vendor's documented per-request output ceiling (distinct from the 1M context) and that the roster's max_completion_tokens is deliberately not treated as the cap
MINOR friendli.json:96 404 rule pattern bare "not found" too broad Resolved — narrowed to quoted-model-id shape, and locked in by testFriendliNotFoundPatternNarrowing (positive quoted-id → InvalidModelError; negative 503 "host not found" → not InvalidModelError)

Verified clean this pass

  • Schema/zod conformance (provider-catalog.schema.json): all required top-level fields present; tier:2 const satisfied; models.default/fallbacks[7]/topModels[7] are all keys of models.catalog; every catalog entry has vision/status/description; capabilities has all 8 booleans (incl. honest structuredOutputWithTools:false).
  • Error-rule semantics (loader.ts buildErrorRules): match is status===statusCode OR pattern.test(msg) (case-insensitive); the friendli 404 rule requires a literal quoted Model '…' not found shape, so a 503 "upstream host not found" cannot be misclassified invalid-model. The E2E regression test asserts exactly this.
  • Backward compatibility (Rule 5): AIProviderName.FRIENDLI, FriendliModels, and NeurolinkCredentials.friendli? are additive-only — no changed functions, no modified existing members.
  • Codegen consistency: index.generated.ts (+friendliJson import/entry, alphabetical), providerCatalog.generated.ts (CatalogProviderName/CatalogCredentialKey +="friendli"), and enums.ts are internally consistent; catalog JSON is statically imported in the registry (Rule 1 governs dynamic provider imports, not catalog JSON).
  • Connectivity/credentials: FRIENDLI_API_KEY wired into live-matrix.yml and FRIENDLI_API_KEY/BASE_URL/MODEL into CATALOG_ENV_VARS; friendli routing case added to CATALOG_ALIAS_CHECKS (url api.friendli.ai/serverless/v1/chat/completions, model zai-org/GLM-5.3), 41 → 42 cases.
  • Security: no secrets in source; workflow pulls the key from secrets.*; mocks-only tests.
  • Graph impact: changed source files are additive enum/type/JSON entries; doc files are line-number anchor shifts only; no out-of-diff caller breakage found.

Findings this pass

None at CRITICAL/MAJOR/MINOR worth an inline comment. The knowledge-graph detect_changes/impact tools were run but the graph is built on the release base (not the friendli head), so the additive-enum blast radius was verified directly from the diff (FRIENDLI sits alphabetically between FIREWORKS/GMICLOUD; FriendliModels between FireworksModels/GmicloudModels) rather than from graph traversal.

Ready to merge.

@murdore
murdore changed the base branch from release to feat/morph-catalog September 13, 2026 22:14
@murdore
murdore force-pushed the feat/friendli-catalog branch from b1215cc to fb203df Compare September 13, 2026 22:16
@murdore
murdore force-pushed the feat/friendli-catalog branch from fb203df to 5374c68 Compare September 19, 2026 01:58
Base automatically changed from feat/morph-catalog to feat/novita-catalog September 19, 2026 04:54
@murdore
murdore force-pushed the feat/novita-catalog branch from 9c2f0c5 to ca731b0 Compare September 19, 2026 05:41
@murdore
murdore force-pushed the feat/friendli-catalog branch from 5374c68 to 80f2af9 Compare September 19, 2026 05:48
@murdore
murdore force-pushed the feat/novita-catalog branch from ca731b0 to d7d0816 Compare September 19, 2026 11:11
@murdore
murdore force-pushed the feat/friendli-catalog branch from 80f2af9 to 91ea950 Compare September 19, 2026 15:25
@murdore
murdore force-pushed the feat/novita-catalog branch from d7d0816 to 1f1297a Compare September 21, 2026 03:55
Adds src/lib/providers/catalog/friendli.json and regenerates the catalog
codegen outputs. Every capability flag is backed by a live wire probe against
https://api.friendli.ai/serverless/v1, not by vendor documentation.

Probed on zai-org/GLM-5.3 (the declared default, and the only roster id
capability-probed end to end):

  tools                     real tool_call, finish_reason=tool_calls
  toolsWithStreaming        9 tool_calls deltas mid-SSE
  streaming                 200, 62 events, terminal [DONE]
  structuredOutput          json_object 200
  structuredOutputWithTools FALSE - HTTP 422, "the 'response_format' field
                            cannot be set when tools are specified"
  vision                    FALSE - HTTP 422, "does not support image/video"
  array message content     accepted, so no messageContentFormat quirk

The tools-plus-schema rejection is a genuine vendor exclusivity of the same
class NeuroLink already handles for Groq. Friendli's 422 body matches
isToolsSchemaConflictError()'s response_format-near-tool pattern, so the
existing generic retry covers it and no provider-specific quirk was added.

The first probe run is deliberately not the source of these flags: a 2s
cadence tripped Friendli's rate limit, 12 of 16 cases returned 429, and the
probe reports any non-200 as a capability rejection - it would have declared
array content unsupported, which is false. The flags come from a re-run paced
at 25s plus targeted single-call re-probes.

Context window, output ceiling and pricing are read from the authenticated
roster response (context_length 1048576, max_completion_tokens 1048576,
per-token pricing scaled to per-MTok), not estimated. The other six roster ids
are verified to exist by id only and say so in their descriptions rather than
carrying invented numbers.

FRIENDLI_API_KEY is wired into the nightly live matrix, and the catalog alias
suite gains a friendli routing case (41 -> 42 cases). The break-one-assertion
ritual was run on that case: deliberately broken it reports a failure and
exits non-zero, restored it passes.

Rebased onto the corrected feat/novita-catalog tip (d7d0816) after that
branch's own rebuild. Only this PR's hand-written files were reapplied
(friendli.json, the live-matrix workflow entry, and the catalog alias test
case) and every derived file was regenerated fresh via codegen:catalog and
docs:api rather than hand-merged, so there is nothing to hand-resolve. Source
diff against the new base: 3 files, +231, and zero deletions.
@murdore
murdore force-pushed the feat/friendli-catalog branch from 91ea950 to 1dac727 Compare September 21, 2026 15:07
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