badgr-cli 1.1.5 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "badgr-cli",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "description": "Badgr — run or serve GPU workloads from one command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -151,7 +151,12 @@ function _resolveHealthPath({ healthPath, isLlamaCpp, customImage, task }) {
151
151
  * directly. The backend does the real health_path probing (see
152
152
  * DeploymentService.check_endpoint_readiness); a pod reporting RUNNING only
153
153
  * means infrastructure is up, not that the app inside is serving.
154
- * Returns { ready: boolean, timedOut: boolean, depFailed: boolean, failReason?: string }
154
+ * Returns { ready: boolean, timedOut: boolean, depFailed: boolean, failReason?: string,
155
+ * dep? }. `dep` (only present when ready) is the last polled
156
+ * /deployments/{id} response — carries supports_completions/
157
+ * supports_chat_completions, probed server-side once the endpoint first
158
+ * reports ready, so the caller's final usage example can match what the
159
+ * model actually serves instead of guessing.
155
160
  */
156
161
  // vLLM cold start (model download + load) often exceeds 5 min on first boot.
157
162
  const VLLM_SERVE_WAIT_MS = 15 * 60 * 1000;
@@ -183,7 +188,7 @@ async function waitForEndpoint(deploymentId, config, timeoutMs = VLLM_SERVE_WAIT
183
188
  }
184
189
  if (dep.endpoint_ready) {
185
190
  if (blockLines > 0) { _clearBlock(blockLines); blockLines = 0; }
186
- return { ready: true, timedOut: false, depFailed: false };
191
+ return { ready: true, timedOut: false, depFailed: false, dep };
187
192
  }
188
193
 
189
194
  const now = Date.now();
@@ -793,6 +798,11 @@ export async function serveCommand(config, args, chalk) {
793
798
  if (endpointReady) {
794
799
  console.log(chalk.dim(_stage(healthStageN, STAGE_TOTAL, 'Checking endpoint health...')));
795
800
  console.log(chalk.green(_stage(readyStageN, STAGE_TOTAL, 'Ready')));
801
+ if (healthResult.dep) {
802
+ dep.supports_completions = healthResult.dep.supports_completions;
803
+ dep.supports_chat_completions = healthResult.dep.supports_chat_completions;
804
+ dep.capability_source = healthResult.dep.capability_source;
805
+ }
796
806
  } else {
797
807
  updateReceipt(rcptId, { status: 'health_check_timeout' });
798
808
  }
@@ -820,7 +830,14 @@ export async function serveCommand(config, args, chalk) {
820
830
  console.log();
821
831
  }
822
832
 
823
- console.log(` ${chalk.bold('Base URL:')} ${chalk.cyan(endpointUrl)}`);
833
+ // dep.service_url is the Badgr-owned https://<id>.serve.aibadgr.com URL
834
+ // once that proxy is actually live (see backend/service_proxy.py) --
835
+ // until then it's absent/equal to endpointUrl, so this falls back to the
836
+ // raw provider URL exactly as before. Only the customer-facing display
837
+ // and usage examples switch to it; polling/comfy-node validation above
838
+ // still talk to the real internal endpointUrl directly.
839
+ const displayUrl = dep.service_url || endpointUrl;
840
+ console.log(` ${chalk.bold('Base URL:')} ${chalk.cyan(displayUrl)}`);
824
841
  if (isLlamaCpp) {
825
842
  console.log(` ${chalk.bold('HF Repo:')} ${flags.hfRepo}`);
826
843
  console.log(` ${chalk.bold('HF File:')} ${flags.hfFile}`);
@@ -843,7 +860,7 @@ export async function serveCommand(config, args, chalk) {
843
860
 
844
861
  if (endpointReady && isOllama) {
845
862
  console.log(` ${chalk.bold('Test with curl:')}`);
846
- console.log(chalk.dim(` curl ${endpointUrl}/api/generate \\`));
863
+ console.log(chalk.dim(` curl ${displayUrl}/api/generate \\`));
847
864
  console.log(chalk.dim(` -H "Content-Type: application/json" \\`));
848
865
  console.log(chalk.dim(` -d '{"model":"${dep.model || effectiveModel}","prompt":"Hello","stream":false}'`));
849
866
  console.log();
@@ -860,13 +877,45 @@ export async function serveCommand(config, args, chalk) {
860
877
  console.log(` ${chalk.bold('API key:')} ${chalk.yellow(dep.endpoint_api_key)}`);
861
878
  console.log(chalk.dim(' Shown once — copy it now. This key is scoped to this endpoint only.'));
862
879
  }
880
+ // dep.supports_chat_completions is probed server-side (see backend
881
+ // model_capabilities.py): a live call against this exact endpoint is
882
+ // authoritative when it's reachable (dep.capability_source ===
883
+ // "runtime"); otherwise it falls back to a best-effort hint from the
884
+ // model's public HF chat-template config (capability_source ===
885
+ // "hf_hint"). `true`/`false` here always mean one of those two actually
886
+ // produced a real signal -- a model without a chat template (e.g.
887
+ // facebook/opt-125m) answers /v1/completions but 400s on
888
+ // /v1/chat/completions, so a generic chat example would be wrong.
889
+ // `undefined`/`null` means NEITHER produced a signal (auth-protected
890
+ // endpoint + HF lookup failed, or a non-vLLM/task image) -- unlike this
891
+ // command's old behavior, that must never silently default to showing
892
+ // a chat example that might not work; show neither example instead.
893
+ const noTask = !flags.task;
894
+ const chatKnown = dep.supports_chat_completions === true || dep.supports_chat_completions === false;
895
+ const showChatExample = noTask && chatKnown && dep.supports_chat_completions === true;
896
+ const showCompletionsExample = noTask && chatKnown && dep.supports_chat_completions === false;
897
+ const capabilityUnverified = noTask && !chatKnown;
898
+ const unconfirmedNote = dep.capability_source === 'hf_hint'
899
+ ? chalk.dim(' (based on the model\'s public Hugging Face config — not confirmed against this live endpoint)')
900
+ : null;
863
901
  console.log(` ${chalk.bold('Test with curl:')}`);
864
- console.log(chalk.dim(` curl ${endpointUrl}/chat/completions \\`));
865
- console.log(chalk.dim(` -H "Authorization: Bearer ${authKey}" -H "Content-Type: application/json" \\`));
866
- console.log(chalk.dim(` -d '{"model":"${sdkModel}","messages":[{"role":"user","content":"Hello"}]}'`));
902
+ if (showChatExample) {
903
+ console.log(chalk.dim(` curl ${displayUrl}/chat/completions \\`));
904
+ console.log(chalk.dim(` -H "Authorization: Bearer ${authKey}" -H "Content-Type: application/json" \\`));
905
+ console.log(chalk.dim(` -d '{"model":"${sdkModel}","messages":[{"role":"user","content":"Hello"}]}'`));
906
+ if (unconfirmedNote) console.log(unconfirmedNote);
907
+ } else if (showCompletionsExample) {
908
+ console.log(chalk.dim(` curl ${displayUrl}/completions \\`));
909
+ console.log(chalk.dim(` -H "Authorization: Bearer ${authKey}" -H "Content-Type: application/json" \\`));
910
+ console.log(chalk.dim(` -d '{"model":"${sdkModel}","prompt":"Hello","max_tokens":32}'`));
911
+ if (unconfirmedNote) console.log(unconfirmedNote);
912
+ } else if (capabilityUnverified) {
913
+ console.log(chalk.yellow(' Capability not verified yet — check available models first:'));
914
+ console.log(chalk.dim(` curl ${displayUrl}/models -H "Authorization: Bearer ${authKey}"`));
915
+ }
867
916
  console.log(` ${chalk.bold('Use with OpenAI SDK:')}`);
868
917
  console.log(chalk.dim(` from openai import OpenAI`));
869
- console.log(chalk.dim(` client = OpenAI(base_url="${endpointUrl}", api_key="${authKey}")`));
918
+ console.log(chalk.dim(` client = OpenAI(base_url="${displayUrl}", api_key="${authKey}")`));
870
919
  if (flags.task === 'transcribe') {
871
920
  console.log(chalk.dim(` with open("audio.mp3", "rb") as f:`));
872
921
  console.log(chalk.dim(` t = client.audio.transcriptions.create(model="${sdkModel}", file=f, response_format="text")`));
@@ -875,8 +924,13 @@ export async function serveCommand(config, args, chalk) {
875
924
  console.log(chalk.dim(` # resp.data[0].b64_json contains the base64-encoded PNG`));
876
925
  } else if (flags.task === 'embed') {
877
926
  console.log(chalk.dim(` resp = client.embeddings.create(model="${sdkModel}", input=["hello world"])`));
878
- } else {
927
+ } else if (showChatExample) {
879
928
  console.log(chalk.dim(` resp = client.chat.completions.create(model="${sdkModel}", messages=[{"role": "user", "content": "Hello"}])`));
929
+ } else if (showCompletionsExample) {
930
+ console.log(chalk.dim(` resp = client.completions.create(model="${sdkModel}", prompt="Hello", max_tokens=32)`));
931
+ console.log(chalk.dim(` # this model has no chat template — /v1/chat/completions returns 400 for it`));
932
+ } else {
933
+ console.log(chalk.dim(` resp = client.models.list() # check capability_unverified — chat vs completions support isn't confirmed yet`));
880
934
  }
881
935
  console.log();
882
936
  if (flags.idleTimeout) {