runbios-sdk 0.2.22 → 0.3.0-dev.323

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/CHANGELOG.md ADDED
@@ -0,0 +1,53 @@
1
+ # Changelog
2
+
3
+ `runbios-sdk` on npm. Versions are 0.x semver: the minor moves when a public
4
+ surface is added, renamed or removed, the patch for a fix inside an unchanged
5
+ surface. The SDK, the Python SDK (`runbios-sdk` on PyPI) and the MCP server
6
+ (`runbios-mcp`) are released together, at one version.
7
+
8
+ ## 0.3.0
9
+
10
+ Added
11
+
12
+ - `training.create` and `training.preflight` take `startFrom.exportStep`
13
+ (`'best'`, the default, or `'final'`): which step of a job trained on top of
14
+ earlier versions its merged model is made of. It needs `exportMerged: true`;
15
+ without it, or with any other value, the SDK refuses before sending, as the
16
+ API does. `TrainingStartFrom` echoes `export_step`.
17
+ - Conscious Loop: `inference` chat and messages calls take
18
+ `loop: { pipeline, onSampleId }`, which sends `X-Loop-Pipeline` and hands
19
+ back the sample id (`loop_sample_id`, or to `onSampleId` before the first
20
+ streamed chunk).
21
+ - Conscious Loop: `loop.listPipelineAttempts` pages a pipeline's attempts;
22
+ `loop.listPipelines` takes `limit`, `q` and `status`; a pipeline's training
23
+ settings (`training`, three-state on a write) and `training_defaults`;
24
+ `loop.listModels` also lists the models not offered, with why;
25
+ `loop.getBenchmarkHistory` takes `pipeline`; `loop.listTraces` takes `q`.
26
+ - Feedback (`signal`, `upvote`, `downvote`, `correct`, `gold`) is sent again
27
+ after `Retry-After` while its sample is still being recorded
28
+ (`409 SAMPLE_NOT_YET_RECORDED`), at most 6 tries; `ApiError` carries
29
+ `retryAfter`.
30
+
31
+ Changed
32
+
33
+ - `LoopSignalParams` takes a person's feedback only: `verdict` is
34
+ `LoopFeedbackVerdict` (`accepted`, `rejected`, `edited` or `gold`) and
35
+ `source` is `'human'`; `score` is gone. The API refuses a `scored` verdict,
36
+ any other source, and a `score` on any verdict with 400.
37
+ - A merge-chain attempt replays about half of what it trains on (was a fifth),
38
+ and a version built on one that learned a deleted conversation says so in
39
+ its lineage.
40
+
41
+ Removed
42
+
43
+ - `Pipeline.champion_version` (use `live_version`) and
44
+ `PipelineVersion.is_champion` (use `is_live`), `benchmark_id` and
45
+ `benchmark_name` (use `listPipelineBenchmarks`), `benchmark_min_delta` (the
46
+ least rise a benchmark must show is the platform's; each measurement's
47
+ `reason` in `promotion` says whether it was met), `auto_promote` (use
48
+ `promotion.policy`), and `monthly_ceiling_cents` and `month_spent_cents`:
49
+ the API no longer sends them. `TrainingRuleMonthlyLimitRefusal` keeps
50
+ `error` and `resumes_at`. The monthly safety limit's figures are not fields
51
+ of any answer, and no sentence states them either: `next_reason` and the
52
+ `MONTHLY_LIMIT_REACHED` message say the limit holds the pipeline and when
53
+ the next attempt can start, with no amount.
package/README.md CHANGED
@@ -100,7 +100,7 @@ without an explicit replay acknowledgement from the endpoint.
100
100
 
101
101
  **Parameter naming on `/v1` calls.** Only the parameters this SDK names are
102
102
  camelCase: `model`, `messages`, `tools`, `toolChoice`, `reasoningEffort`,
103
- `inferenceKey`, `idempotencyKey`, `requestId`, `signal`. Every other key is
103
+ `inferenceKey`, `idempotencyKey`, `requestId`, `signal`, `loop`. Every other key is
104
104
  forwarded to the OpenAI-compatible endpoint **verbatim**, so it must use
105
105
  OpenAI's own snake_case spelling — `max_tokens`, `top_p`, `stop`,
106
106
  `frequency_penalty`, `presence_penalty`. The endpoint rejects an unknown key
@@ -217,6 +217,79 @@ for await (const event of dedicated.streamCompletions({ model: 'completion-deplo
217
217
  methods close the upstream reader when iteration ends. None of the inference
218
218
  POSTs are retried automatically, and `idempotencyKey` is sent only if supplied.
219
219
 
220
+ #### Record conversations into a Conscious Loop pipeline (one header)
221
+
222
+ Add `loop: { pipeline: '<tag>' }` to `chatCompletions`, `streamChatCompletions`,
223
+ `messages` or `streamMessages` and the call is sent exactly as before with one
224
+ more header, `X-Loop-Pipeline: <tag>` (several tags: `pipeline: ['a', 'b']`).
225
+ The gateway records the exchange -- what was asked, what the model answered and
226
+ its tool calls -- as a sample of the pipelines those tags feed (a pipeline's
227
+ `own_tag`), even where recording is off for that model: the header is your
228
+ consent for that one exchange. With `chatCompletions` and `streamChatCompletions`
229
+ it works for serverless models and dedicated deployments, including the
230
+ pipeline's own `loop-<tag>` model. `messages` and `streamMessages` (the
231
+ Anthropic shape) work for serverless models; a dedicated deployment, and so a
232
+ pipeline's `loop-<tag>` model, serves the OpenAI shape and refuses
233
+ `/v1/messages` with 400, so call it with `chatCompletions`. Only the tags of a
234
+ pipeline that exists are recorded: a call naming a misspelt tag, or a deleted
235
+ pipeline's, is not recorded under it, and `client.loop.stats()` lists such
236
+ calls under `capture`.
237
+
238
+ The gateway answers with the sample's id in `X-Loop-Sample-Id`. A non-streaming
239
+ result carries it as `loop_sample_id` (non-enumerable: it is never part of the
240
+ answer's JSON), and `loop.onSampleId` hears it as soon as the headers arrive,
241
+ before the first streamed chunk. It is `null` when the gateway recorded nothing.
242
+ Send feedback on that id; it is the trace id the feedback calls take.
243
+
244
+ ```typescript
245
+ const reply = await client.inference.chatCompletions({
246
+ model: 'loop-support-replies',
247
+ messages: [{ role: 'user', content: 'Where is my order?' }],
248
+ loop: { pipeline: 'support-replies' },
249
+ });
250
+ const sampleId = reply.loop_sample_id;
251
+ if (sampleId) {
252
+ await client.loop.upvote(sampleId); // a good answer
253
+ // await client.loop.downvote(sampleId); // a bad one
254
+ // await client.loop.correct(sampleId, 'The right answer.');
255
+ }
256
+
257
+ for await (const chunk of client.inference.streamChatCompletions({
258
+ model: 'loop-support-replies',
259
+ messages: [{ role: 'user', content: 'Where is my order?' }],
260
+ loop: { pipeline: 'support-replies', onSampleId: (id) => console.log('sample', id) },
261
+ })) {
262
+ process.stdout.write(String((chunk.choices as any)?.[0]?.delta?.content ?? ''));
263
+ }
264
+ ```
265
+
266
+ Feedback sent before the sample is recorded is refused `409
267
+ SAMPLE_NOT_YET_RECORDED`, and nothing is stored. `upvote`, `downvote`,
268
+ `correct`, `gold` and `signal` send it again after the `Retry-After` seconds (2
269
+ when none is given), at most 6 times in all, then throw that error. A stream
270
+ that was cut short is never recorded, so its id stays unknown (it is counted in
271
+ `client.loop.stats()` under `capture`). If you send
272
+ `X-Request-ID`, keep it unique per call: a repeated one is recorded once.
273
+ `completions`, `embeddings` and `rerank` are not recorded and refuse the option.
274
+
275
+ Many feedback items at once: `signalMany` sends each as `signal` does, the same
276
+ retry included, with up to 8 in flight (`{ concurrency }`, at most 32), and
277
+ returns every item in order with its `signal` or its `error`:
278
+
279
+ ```typescript
280
+ const results = await client.loop.signalMany([
281
+ { trace_id: idA, verdict: 'good' },
282
+ { trace_id: idB, verdict: 'corrected', correction: 'Refunds take five working days.' },
283
+ ]);
284
+ const failed = results.filter((r) => r.error);
285
+ ```
286
+
287
+ Sending a sample yourself with `client.loop.capture` and already know the
288
+ verdict? Pass `feedback: { verdict: 'good' | 'bad' | 'corrected', correction }`
289
+ and it is written with the sample, in one transaction: both are stored or
290
+ neither is. `good`, `bad` and `corrected` work in `signal` and `signalMany` too,
291
+ stored as `accepted`, `rejected` and `edited`.
292
+
220
293
  #### Workspace serverless usage and limits (read-only)
221
294
 
222
295
  A workspace-bound platform key or hosted OAuth grant with `analytics:read` can
package/dist/client.d.ts CHANGED
@@ -50,8 +50,19 @@ export declare class ApiError extends Error {
50
50
  readonly availableGpus: AvailableGpuAlternative[] | undefined;
51
51
  /** Availability snapshot time behind an availability rejection. */
52
52
  readonly checkedAt: string | undefined;
53
+ /**
54
+ * How many seconds the service asked you to wait before trying again: its
55
+ * `Retry-After` header, as seconds or as a date. Undefined when it sent none.
56
+ */
57
+ retryAfter: number | undefined;
53
58
  constructor(status: number, body: ApiErrorBody);
54
59
  }
60
+ /**
61
+ * Seconds to wait from a `Retry-After` header: a number of seconds, or an HTTP
62
+ * date (seconds until then, never negative). Undefined when absent or
63
+ * unreadable. @internal
64
+ */
65
+ export declare function retryAfterSeconds(value: string | null | undefined, now?: number): number | undefined;
55
66
  /**
56
67
  * The one transient GPU-rejection code: the GPU is not in stock right now, so
57
68
  * waiting or joining the capacity queue can still succeed.
package/dist/client.js CHANGED
@@ -53,6 +53,11 @@ export class ApiError extends Error {
53
53
  availableGpus;
54
54
  /** Availability snapshot time behind an availability rejection. */
55
55
  checkedAt;
56
+ /**
57
+ * How many seconds the service asked you to wait before trying again: its
58
+ * `Retry-After` header, as seconds or as a date. Undefined when it sent none.
59
+ */
60
+ retryAfter;
56
61
  constructor(status, body) {
57
62
  const err = body.error;
58
63
  const nested = typeof err === 'object' && err !== null ? err : null;
@@ -71,8 +76,29 @@ export class ApiError extends Error {
71
76
  const alts = flat.available_gpus ?? flat.available_alternatives;
72
77
  this.availableGpus = Array.isArray(alts) && alts.length > 0 ? alts : undefined;
73
78
  this.checkedAt = flat.checked_at;
79
+ this.retryAfter = undefined;
74
80
  }
75
81
  }
82
+ /**
83
+ * Seconds to wait from a `Retry-After` header: a number of seconds, or an HTTP
84
+ * date (seconds until then, never negative). Undefined when absent or
85
+ * unreadable. @internal
86
+ */
87
+ export function retryAfterSeconds(value, now = Date.now()) {
88
+ const raw = (value ?? '').trim();
89
+ if (!raw)
90
+ return undefined;
91
+ if (/^\d+(\.\d+)?$/.test(raw))
92
+ return Number(raw);
93
+ const at = Date.parse(raw);
94
+ return Number.isNaN(at) ? undefined : Math.max(0, (at - now) / 1000);
95
+ }
96
+ /** The error a non-2xx response stands for, with its `Retry-After`. @internal */
97
+ function errorFromResponse(res, data) {
98
+ const err = buildApiError(res.status, data);
99
+ err.retryAfter = retryAfterSeconds(res.headers.get('retry-after'));
100
+ return err;
101
+ }
76
102
  /**
77
103
  * Flatten the standard `{ error: { ... } }` envelope over the legacy top-level
78
104
  * fields so structured capacity data is found in either shape. @internal
@@ -344,7 +370,7 @@ export class HttpClient {
344
370
  constructor(config) {
345
371
  // Default host stays api.runbios.ai for now; cutover to api.runbios.ai is
346
372
  // planned once its DNS exists.
347
- this.baseUrl = (config.baseUrl || envBaseUrl() || 'https://api.runbios.ai').replace(/\/+$/, '');
373
+ this.baseUrl = (config.baseUrl || envBaseUrl() || 'https://api-dev.runbios.ai').replace(/\/+$/, '');
348
374
  this.apiKey = config.apiKey ?? envApiKey();
349
375
  this.accessToken = config.accessToken;
350
376
  this.orgId = config.orgId;
@@ -431,7 +457,7 @@ export class HttpClient {
431
457
  }
432
458
  const data = await res.json().catch(() => ({}));
433
459
  if (!res.ok) {
434
- throw buildApiError(res.status, data);
460
+ throw errorFromResponse(res, data);
435
461
  }
436
462
  return data;
437
463
  }
@@ -486,7 +512,7 @@ export class HttpClient {
486
512
  }
487
513
  const data = await res.json().catch(() => ({}));
488
514
  if (!res.ok) {
489
- throw buildApiError(res.status, data);
515
+ throw errorFromResponse(res, data);
490
516
  }
491
517
  return data;
492
518
  }
package/dist/index.d.ts CHANGED
@@ -36,7 +36,7 @@ import { Loop } from './resources/loop.js';
36
36
  * SDK version. Sent as part of the User-Agent header.
37
37
  * Must match package.json "version" -- enforced by a contract test.
38
38
  */
39
- export declare const VERSION = "0.2.22";
39
+ export declare const VERSION = "0.3.0-dev.323";
40
40
  export declare class RunBiOS {
41
41
  /** Search models, fetch configs, check adapter compatibility. */
42
42
  readonly models: Models;
@@ -74,8 +74,8 @@ export { Integrations, type HuggingFaceIntegration, type IntegrationBrowseParams
74
74
  export { Training } from './resources/training.js';
75
75
  export { Wallet } from './resources/wallet.js';
76
76
  export { GPU, type GPURecommendation } from './resources/gpu.js';
77
- export { Inference, validateChatRequest, parseSSE, buildInferenceRequest, contextSizingBasis, type ContextSizingBasis, CONTEXT_DEFAULT_CEILING, CONTEXT_EDITABLE_FLOOR, type ChatMessage, type FunctionTool, type ChatCompletionParams, type ChatCompletionResponse, type ChatCompletionChunk, type InferenceVerbParams, type CompletionParams, type EmbeddingParams, type RerankParams, type AnthropicMessageParams, type ServerlessUsageWindow, type ServerlessTimeseriesMetric, type ServerlessWorkspaceLimits, type ServerlessUsageEnvelope, type ServerlessUsageOverviewResponse, type ServerlessUsageRowsResponse, type ServerlessUsageTimeseriesResponse, type ServerlessUsageDailyResponse, type ServerlessSavingsTotals, type ServerlessSavingsPeriod, type ServerlessUsageSavingsResponse, } from './resources/inference.js';
78
- export type { BiOSConfig, PaginatedResponse, ApiErrorBody, AvailableGpuAlternative, CapacityMinimumRequirement, InferenceBookingAccepted, Model, ModelSearchParams, ModelSearchResponse, ModelDetailResponse, InferenceModel, InferenceModelListResponse, ModelConfig, Dataset, DatasetListParams, DatasetListResponse, DatasetUploadParams, DatasetPreview, DatasetPreviewParams, DatasetImportHFParams, DatasetRegisterHFParams, DatasetHubSearchParams, DatasetHubPreviewParams, DatasetValidation, DatasetFormatVariant, DatasetFormatSpec, DatasetFormatSpecs, DatasetStorageUsage, TrainingMethod, RLHFAlgorithm, AdapterType, TrainingJobStatus, TrainingStatusFilter, TrainingCreateParams, TrainingStartFromParams, TrainingStartFrom, TrainingListParams, TrainingListResponse, TrainingJob, TrainingMetrics, TrainingResourceMetrics, TrainingDeviceMetrics, MetricPoint, MetricGraphConfig, TrainingCheckpoint, TrainingLogs, TrainingLogEntry, TrainingStopResponse, TrainingResumeResponse, CanonicalTrainingRequest, TrainingPreflightDataset, TrainingPreflightWarning, TrainingPreflightResponse, TrainingCapabilityChoice, TrainingConfigFieldCapability, TrainingCapabilities, TrainingFeature, GPUChoice, WalletBalance, Transaction, TransactionListResponse, TransactionListParams, GPUInfo, GPUPricingResponse, GPUOptionsParams, TrainingRecommendParams, TrainingAdvisorVerdict, TrainingAdvisorRecommendation, TrainingAdvisorUserShape, TrainingAdvisorJustification, TrainingAdvisorBasis, GPUOption, GPUOptionSuggestion, GPUOptionsResponse, InferenceStatus, InferenceCreateParams, InferenceUpdateParams, InferenceDeployment, InferenceDeploymentSummary, InferenceCreateResponse, InferenceListResponse, InferenceUpdateResponse, InferenceLifecycleResponse, InferenceDeleteResponse, InferenceGPUOptionMarket, InferenceGPUOption, InferenceGPUAlternative, InferenceGPUOptionsParams, InferenceGPUOptionsResponse, InferenceCanonicalRequest, InferencePreflightWarning, InferencePreflightResponse, AdapterCompatibility, AdapterCompatibilityResponse, AdapterCompatibilityParams, SupportedArchitecture, ArchitectureScope, SupportedArchitecturesResponse, ApiKey, ApiKeyScope, ApiKeyIntrospection, Organization, OrgMember, OrgInvite, Workspace, Integration, IntegrationCreateParams, StorageUsage, StorageObject, PromptTokensDetails, ChatCompletionUsage, ServingKind, TrainingRulePausedReason, TrainingTrigger, TrainingRecipe, TrainingRunState, TrainingVerdict, TrainingDecision, EvaluationStatus, EvaluationItemStatus, ItemErrorCode, EvaluationWinner, EvaluationItemWinnerFilter, ReportStatusFilter, ReportSort, EvaluationWarningCode, TrainingToolCall, TrainingMessage, TrainingRuleServing, TrainingRunPromoteRequest, TrainingRunRejectRequest, TrainingRunRollbackRequest, TrainingRunCancelRequest, TrainingRunListParams, TrainingRun, TrainingRunSummary, TrainingRunEvent, TrainingRunLinks, TrainingRunActions, EvaluationRef, EvaluationDecoding, EvaluationJudgeDimension, EvaluationDimensionScore, EvaluationWarning, EvaluationMargin, Evaluation, EvaluationItemJudge, EvaluationReferenceKind, EvaluationReference, EvaluationPairwiseOrder, EvaluationItemPairwise, EvaluationItemSide, EvaluationItem, EvaluationItemListParams, BenchmarkStatus, BenchmarkSourceKind, BenchmarkRunStatus, BenchmarkScoring, BenchmarkCheck, BenchmarkDimensionScore, Benchmark, BenchmarkItem, BenchmarkRun, BenchmarkRunItemSide, BenchmarkRunItem, BenchmarkRunItemListParams, BenchmarkHistoryPoint, BenchmarkSource, BenchmarkCreateParams, BenchmarkListParams, BenchmarkItemListParams, BenchmarkHistoryParams, BenchmarkRetireParams, InferenceAlias, InferenceAliasRequest, TrainingRuleDeleteResponse, TrainingRunListResponse, TrainingRunBenchmarkRun, TrainingRunResponse, TrainingRunActionResponse, EvaluationItemsResponse, BenchmarkRunItemsResponse, InferenceAliasListResponse, InferenceAliasResponse, InferenceAliasDeleteResponse, BenchmarkListResponse, BenchmarkItemsResponse, BenchmarkHistoryResponse, TrainingRuleRunRefusalCode, TrainingRuleSaveRefusalCode, TrainingRuleMonthlyLimitRefusal, PipelineStatus, PipelineActiveRun, PipelineVersion, PipelineAttempt, PipelineAttemptTrigger, Lineage, PipelineChain, PipelineChainRebuild, TrainingRunRollbackRefusalCode, LoopTraceRebuild, LoopTraceDeleteResponse, PipelineAttemptOutcome, PipelineLiveDeploymentStatus, PipelineLiveDeployment, LoopStorageWhere, LoopStorage, Pipeline, PipelineListParams, PipelineListResponse, PipelineResponse, PipelineTrainWhen, PipelineSchedule, PipelineWriteRequest, PipelineMutationResponse, PipelineImportParams, PipelineFloors, HeldBackFloors, LoopModel, LoopModelsResponse, LoopTracePipeline, LoopTracePipelinesResponse, LoopMetricsHistoryParams, LoopMetricPoint, LoopSignalDay, LoopMetricsHistory, PromotionPolicyState, RuleBenchmark, PipelineBenchmarksResponse, PipelineBenchmarkAttachParams, PipelineBenchmarkMoveParams, CompareBenchmark, VersionBenchmarkScore, PromotionMeasurement, PromotionOutcome, VersionHoldout, VersionScores, VersionScoreDifference, VersionCompareResponse, VersionCompareParams, TrainingRunPromotion, VerdictCheck, VerdictCheckKey, VerdictChecksWithheld, TrainingRunCost, AttemptDirection, AttemptHoldout, AttemptCompareSide, AttemptSharedSample, AttemptSharedFigures, AttemptBenchmarkSide, AttemptCompareBenchmark, AttemptCompareItemSide, AttemptCompareItem, AttemptCompareResponse, AttemptCompareParams, } from './types.js';
77
+ export { Inference, validateChatRequest, parseSSE, buildInferenceRequest, contextSizingBasis, type ContextSizingBasis, CONTEXT_DEFAULT_CEILING, CONTEXT_EDITABLE_FLOOR, type ChatMessage, type FunctionTool, type ChatCompletionParams, type ChatCompletionResponse, type ChatCompletionChunk, type InferenceVerbParams, type CompletionParams, type EmbeddingParams, type RerankParams, type AnthropicMessageParams, type AnthropicMessageResponse, type LoopCaptureOption, type LoopSampleIdOnResult, LOOP_PIPELINE_HEADER, LOOP_SAMPLE_ID_HEADER, type ServerlessUsageWindow, type ServerlessTimeseriesMetric, type ServerlessWorkspaceLimits, type ServerlessUsageEnvelope, type ServerlessUsageOverviewResponse, type ServerlessUsageRowsResponse, type ServerlessUsageTimeseriesResponse, type ServerlessUsageDailyResponse, type ServerlessSavingsTotals, type ServerlessSavingsPeriod, type ServerlessUsageSavingsResponse, } from './resources/inference.js';
78
+ export type { BiOSConfig, PaginatedResponse, ApiErrorBody, AvailableGpuAlternative, CapacityMinimumRequirement, InferenceBookingAccepted, Model, ModelSearchParams, ModelSearchResponse, ModelDetailResponse, InferenceModel, InferenceModelListResponse, ModelConfig, Dataset, DatasetListParams, DatasetListResponse, DatasetUploadParams, DatasetPreview, DatasetPreviewParams, DatasetImportHFParams, DatasetRegisterHFParams, DatasetHubSearchParams, DatasetHubPreviewParams, DatasetValidation, DatasetFormatVariant, DatasetFormatSpec, DatasetFormatSpecs, DatasetStorageUsage, TrainingMethod, RLHFAlgorithm, AdapterType, TrainingJobStatus, TrainingStatusFilter, TrainingCreateParams, TrainingStartFromParams, TrainingStartFrom, TrainingExportStep, TrainingListParams, TrainingListResponse, TrainingJob, TrainingMetrics, TrainingResourceMetrics, TrainingDeviceMetrics, MetricPoint, MetricGraphConfig, TrainingCheckpoint, TrainingLogs, TrainingLogEntry, TrainingStopResponse, TrainingResumeResponse, CanonicalTrainingRequest, TrainingPreflightDataset, TrainingPreflightWarning, TrainingPreflightResponse, TrainingCapabilityChoice, TrainingConfigFieldCapability, TrainingCapabilities, TrainingFeature, GPUChoice, WalletBalance, Transaction, TransactionListResponse, TransactionListParams, GPUInfo, GPUPricingResponse, GPUOptionsParams, TrainingRecommendParams, TrainingAdvisorVerdict, TrainingAdvisorRecommendation, TrainingAdvisorUserShape, TrainingAdvisorJustification, TrainingAdvisorBasis, GPUOption, GPUOptionSuggestion, GPUOptionsResponse, InferenceStatus, InferenceCreateParams, InferenceUpdateParams, InferenceDeployment, InferenceDeploymentSummary, InferenceCreateResponse, InferenceListResponse, InferenceUpdateResponse, InferenceLifecycleResponse, InferenceDeleteResponse, InferenceGPUOptionMarket, InferenceGPUOption, InferenceGPUAlternative, InferenceGPUOptionsParams, InferenceGPUOptionsResponse, InferenceCanonicalRequest, InferencePreflightWarning, InferencePreflightResponse, AdapterCompatibility, AdapterCompatibilityResponse, AdapterCompatibilityParams, SupportedArchitecture, ArchitectureScope, SupportedArchitecturesResponse, ApiKey, ApiKeyScope, ApiKeyIntrospection, Organization, OrgMember, OrgInvite, Workspace, Integration, IntegrationCreateParams, StorageUsage, StorageObject, PromptTokensDetails, ChatCompletionUsage, ServingKind, TrainingRulePausedReason, TrainingTrigger, TrainingRecipe, TrainingRunState, TrainingVerdict, TrainingDecision, EvaluationStatus, EvaluationItemStatus, ItemErrorCode, EvaluationWinner, EvaluationItemWinnerFilter, ReportStatusFilter, ReportSort, EvaluationWarningCode, TrainingToolCall, TrainingMessage, TrainingRuleServing, TrainingRunPromoteRequest, TrainingRunRejectRequest, TrainingRunRollbackRequest, TrainingRunCancelRequest, TrainingRunListParams, TrainingRun, TrainingRunSummary, TrainingRunEvent, TrainingRunLinks, TrainingRunActions, EvaluationRef, EvaluationDecoding, EvaluationJudgeDimension, EvaluationDimensionScore, EvaluationWarning, EvaluationMargin, Evaluation, EvaluationItemJudge, EvaluationReferenceKind, EvaluationReference, EvaluationPairwiseOrder, EvaluationItemPairwise, EvaluationItemSide, EvaluationItem, EvaluationItemListParams, BenchmarkStatus, BenchmarkSourceKind, BenchmarkRunStatus, BenchmarkScoring, BenchmarkCheck, BenchmarkDimensionScore, Benchmark, BenchmarkItem, BenchmarkRun, BenchmarkRunItemSide, BenchmarkRunItem, BenchmarkRunItemListParams, BenchmarkHistoryPoint, BenchmarkSource, BenchmarkCreateParams, BenchmarkListParams, BenchmarkItemListParams, BenchmarkHistoryParams, BenchmarkRetireParams, InferenceAlias, InferenceAliasRequest, TrainingRuleDeleteResponse, TrainingRunListResponse, TrainingRunBenchmarkRun, TrainingRunResponse, TrainingRunActionResponse, EvaluationItemsResponse, BenchmarkRunItemsResponse, InferenceAliasListResponse, InferenceAliasResponse, InferenceAliasDeleteResponse, BenchmarkListResponse, BenchmarkItemsResponse, BenchmarkHistoryResponse, TrainingRuleRunRefusalCode, TrainingRuleSaveRefusalCode, TrainingRuleMonthlyLimitRefusal, PipelineStatus, PipelineActiveRun, PipelineVersion, PipelineAttempt, AttemptExplanation, PipelineAttemptListParams, PipelineAttemptListResponse, PipelineAttemptModelFilter, PipelineAttemptResultFilter, PipelineAttemptTrigger, Lineage, PipelineChain, PipelineChainRebuild, TrainingRunRollbackRefusalCode, LoopTraceRebuild, LoopTraceDeleteResponse, PipelineAttemptOutcome, PipelineLiveDeploymentStatus, PipelineLiveDeployment, LoopStorageWhere, LoopStorage, Pipeline, PipelineListParams, PipelineListStatus, PipelineListResponse, PipelineTrainingSettings, PipelineTrainingDefaults, PipelineTrainingRequest, PipelineResponse, PipelineTrainWhen, PipelineSchedule, PipelineWriteRequest, PipelineMutationResponse, PipelineImportParams, PipelineFloors, HeldBackFloors, LoopModel, LoopModelsResponse, LoopUnavailableModel, LoopUnavailableReasonCode, LoopTracePipeline, LoopTracePipelinesResponse, LoopMetricsHistoryParams, LoopMetricPoint, LoopSignalDay, LoopMetricsHistory, LoopCaptureFeedback, LoopFeedbackWord, LoopCaptureHealth, LoopCaptureMiss, LoopSignalManyItem, LoopSignalManyResult, PromotionPolicyState, RuleBenchmark, PipelineBenchmarksResponse, PipelineBenchmarkAttachParams, PipelineBenchmarkMoveParams, CompareBenchmark, VersionBenchmarkScore, PromotionMeasurement, PromotionOutcome, VersionHoldout, VersionScores, VersionScoreDifference, VersionCompareResponse, VersionCompareParams, TrainingRunPromotion, VerdictCheck, VerdictCheckKey, VerdictChecksWithheld, TrainingRunCost, AttemptDirection, AttemptHoldout, AttemptCompareSide, AttemptSharedSample, AttemptSharedFigures, AttemptBenchmarkSide, AttemptCompareBenchmark, AttemptCompareItemSide, AttemptCompareItem, AttemptCompareResponse, AttemptCompareParams, } from './types.js';
79
79
  /** The closed set of run states a training run never leaves. */
80
80
  export { TERMINAL_RUN_STATES } from './types.js';
81
81
  /** The most versions a pipeline may be set to make. */
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ import { Loop } from './resources/loop.js';
36
36
  * SDK version. Sent as part of the User-Agent header.
37
37
  * Must match package.json "version" -- enforced by a contract test.
38
38
  */
39
- export const VERSION = '0.2.22';
39
+ export const VERSION = '0.3.0-dev.323';
40
40
  export class RunBiOS {
41
41
  /** Search models, fetch configs, check adapter compatibility. */
42
42
  models;
@@ -102,7 +102,7 @@ export { Integrations } from './resources/integrations.js';
102
102
  export { Training } from './resources/training.js';
103
103
  export { Wallet } from './resources/wallet.js';
104
104
  export { GPU } from './resources/gpu.js';
105
- export { Inference, validateChatRequest, parseSSE, buildInferenceRequest, contextSizingBasis, CONTEXT_DEFAULT_CEILING, CONTEXT_EDITABLE_FLOOR, } from './resources/inference.js';
105
+ export { Inference, validateChatRequest, parseSSE, buildInferenceRequest, contextSizingBasis, CONTEXT_DEFAULT_CEILING, CONTEXT_EDITABLE_FLOOR, LOOP_PIPELINE_HEADER, LOOP_SAMPLE_ID_HEADER, } from './resources/inference.js';
106
106
  /** The closed set of run states a training run never leaves. */
107
107
  export { TERMINAL_RUN_STATES } from './types.js';
108
108
  /** The most versions a pipeline may be set to make. */
@@ -49,6 +49,54 @@ export interface FunctionTool extends Record<string, unknown> {
49
49
  strict?: boolean;
50
50
  };
51
51
  }
52
+ /**
53
+ * Record one exchange into Conscious Loop pipelines with one header (the
54
+ * `loop` option of `chatCompletions`, `streamChatCompletions`, `messages` and
55
+ * `streamMessages`).
56
+ *
57
+ * The call goes to your model exactly as it would without it, with
58
+ * `X-Loop-Pipeline: <tag>[, <tag>...]` added: the gateway records the
59
+ * exchange (what was asked, what the model answered, its tool calls) as a
60
+ * sample of the pipelines those tags feed -- a pipeline's `own_tag` -- even
61
+ * where recording is off for that model, because the header is your consent
62
+ * for this one exchange. It answers with the sample's id in
63
+ * `X-Loop-Sample-Id`, before the body when streaming. Send feedback on that id
64
+ * with `client.loop.upvote`, `downvote` or `correct`: the sample id is the
65
+ * `traceId` they take. Feedback sent before the sample is recorded can be
66
+ * refused 409 `SAMPLE_NOT_YET_RECORDED`; send it again after the
67
+ * `Retry-After` seconds.
68
+ *
69
+ * Works on `/v1/chat/completions` for serverless models and dedicated
70
+ * deployments, a pipeline's own `loop-<tag>` model included, and on
71
+ * `/v1/messages` for serverless models: a dedicated deployment serves the
72
+ * OpenAI shape and refuses an Anthropic body with 400. Only the tags of a
73
+ * pipeline that exists are recorded; `client.loop.stats()` lists the calls
74
+ * that were not, under `capture`.
75
+ */
76
+ export interface LoopCaptureOption {
77
+ /** One pipeline tag, or several; each a non-empty tag with no comma. */
78
+ pipeline: string | string[];
79
+ /**
80
+ * Called once with the sample id as soon as the response headers arrive
81
+ * (before the first streamed chunk), or with null when the gateway recorded
82
+ * nothing for this exchange (no `X-Loop-Sample-Id` came back). A
83
+ * non-streaming result also carries it as `loop_sample_id`.
84
+ */
85
+ onSampleId?: (sampleId: string | null) => void;
86
+ }
87
+ /** The request header the `loop` option sends. */
88
+ export declare const LOOP_PIPELINE_HEADER = "X-Loop-Pipeline";
89
+ /** The response header the gateway answers a recorded exchange with: the sample's id. */
90
+ export declare const LOOP_SAMPLE_ID_HEADER = "X-Loop-Sample-Id";
91
+ /**
92
+ * A non-streaming `/v1` answer, as the endpoint sent it, plus
93
+ * `loop_sample_id`: the `X-Loop-Sample-Id` the gateway answered with, or null
94
+ * when it sent none. It is NON-ENUMERABLE, so it is never part of the
95
+ * object's JSON or its keys; read it by name.
96
+ */
97
+ export interface LoopSampleIdOnResult {
98
+ readonly loop_sample_id?: string | null;
99
+ }
52
100
  export interface ChatCompletionParams extends Record<string, unknown> {
53
101
  messages: ChatMessage[];
54
102
  model?: string;
@@ -75,8 +123,10 @@ export interface ChatCompletionParams extends Record<string, unknown> {
75
123
  idempotencyKey?: string;
76
124
  requestId?: string;
77
125
  signal?: AbortSignal;
126
+ /** Record this exchange into Conscious Loop pipelines. See {@link LoopCaptureOption}. */
127
+ loop?: LoopCaptureOption;
78
128
  }
79
- export type ChatCompletionResponse = Record<string, unknown>;
129
+ export type ChatCompletionResponse = Record<string, unknown> & LoopSampleIdOnResult;
80
130
  export type ChatCompletionChunk = Record<string, unknown>;
81
131
  export interface InferenceVerbParams extends Record<string, unknown> {
82
132
  model?: string;
@@ -99,7 +149,11 @@ export interface AnthropicMessageParams extends InferenceVerbParams {
99
149
  messages: Array<Record<string, unknown>>;
100
150
  max_tokens: number;
101
151
  anthropicVersion?: string;
152
+ /** Record this exchange into Conscious Loop pipelines. See {@link LoopCaptureOption}. */
153
+ loop?: LoopCaptureOption;
102
154
  }
155
+ /** A non-streaming `/v1/messages` answer, with `loop_sample_id` (see {@link LoopSampleIdOnResult}). */
156
+ export type AnthropicMessageResponse = Record<string, unknown> & LoopSampleIdOnResult;
103
157
  export type ServerlessUsageWindow = '1h' | '24h' | '7d' | '30d' | '90d';
104
158
  export type ServerlessTimeseriesMetric = 'requests' | 'tokens' | 'spend' | 'ttft_p50' | 'ttft_p95' | 'tps';
105
159
  export interface ServerlessWorkspaceLimits {
@@ -359,12 +413,30 @@ export declare class Inference {
359
413
  completions(params: CompletionParams): Promise<Record<string, unknown>>;
360
414
  embeddings(params: EmbeddingParams): Promise<Record<string, unknown>>;
361
415
  rerank(params: RerankParams): Promise<Record<string, unknown>>;
362
- messages(params: AnthropicMessageParams): Promise<Record<string, unknown>>;
416
+ /**
417
+ * Anthropic Messages (`/v1/messages`). With `loop: { pipeline }` the
418
+ * exchange is recorded into those pipelines and the result carries
419
+ * `loop_sample_id` (see {@link LoopCaptureOption}). For serverless models:
420
+ * a dedicated deployment, a pipeline's `loop-<tag>` model included, serves
421
+ * the OpenAI shape and refuses this with 400; call it with `chatCompletions`.
422
+ */
423
+ messages(params: AnthropicMessageParams): Promise<AnthropicMessageResponse>;
363
424
  streamCompletions(params: CompletionParams): AsyncGenerator<Record<string, unknown>>;
425
+ /** Anthropic Messages, streamed. With `loop`, `loop.onSampleId` hears the sample id before the first event. */
364
426
  streamMessages(params: AnthropicMessageParams): AsyncGenerator<Record<string, unknown>>;
365
427
  listModels(inferenceKey?: string): Promise<InferenceModelListResponse>;
366
428
  retrieveModel(modelId: string, inferenceKey?: string): Promise<InferenceModel>;
429
+ /**
430
+ * OpenAI chat completions (`/v1/chat/completions`). With
431
+ * `loop: { pipeline: 'support-replies' }` the exchange is recorded into
432
+ * that pipeline and the result carries `loop_sample_id`, the id to send
433
+ * feedback on (see {@link LoopCaptureOption}).
434
+ */
367
435
  chatCompletions(params: ChatCompletionParams): Promise<ChatCompletionResponse>;
436
+ /**
437
+ * OpenAI chat completions, streamed. With `loop`, `loop.onSampleId` hears
438
+ * the sample id as soon as the headers arrive, before the first chunk.
439
+ */
368
440
  streamChatCompletions(params: ChatCompletionParams): AsyncGenerator<ChatCompletionChunk>;
369
441
  }
370
442
  export { buildInferenceRequest };
@@ -135,6 +135,43 @@ function buildInferenceRequest(params) {
135
135
  body.max_price_hour_cents = params.maxPriceHourCents;
136
136
  return body;
137
137
  }
138
+ /** The request header the `loop` option sends. */
139
+ export const LOOP_PIPELINE_HEADER = 'X-Loop-Pipeline';
140
+ /** The response header the gateway answers a recorded exchange with: the sample's id. */
141
+ export const LOOP_SAMPLE_ID_HEADER = 'X-Loop-Sample-Id';
142
+ /** `X-Loop-Pipeline`'s value for a `loop` option, or a thrown Error before any request. */
143
+ function loopPipelineHeader(loop) {
144
+ if (!loop || typeof loop !== 'object')
145
+ throw new Error('loop must be an object: { pipeline }');
146
+ const tags = Array.isArray(loop.pipeline) ? loop.pipeline : [loop.pipeline];
147
+ if (tags.length === 0)
148
+ throw new Error('loop.pipeline must name at least one pipeline tag');
149
+ return tags.map((tag) => {
150
+ const value = typeof tag === 'string' ? tag.trim() : '';
151
+ // A comma would split one tag into two, and a control character would end
152
+ // the header: both are refused here rather than sent.
153
+ if (!value || /[,\u0000-\u001f\u007f]/.test(value)) {
154
+ throw new Error(`loop.pipeline: ${JSON.stringify(tag)} is not a pipeline tag`);
155
+ }
156
+ return value;
157
+ }).join(', ');
158
+ }
159
+ /** The sample id a response carries (null when none), told to the caller's onSampleId once. */
160
+ function takeLoopSampleId(response, loop) {
161
+ const id = response.headers.get(LOOP_SAMPLE_ID_HEADER)?.trim() || null;
162
+ if (loop?.onSampleId)
163
+ loop.onSampleId(id);
164
+ return id;
165
+ }
166
+ /** `loop_sample_id` on a non-streaming result, non-enumerable. */
167
+ function withLoopSampleId(payload, id) {
168
+ if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
169
+ Object.defineProperty(payload, 'loop_sample_id', {
170
+ value: id, enumerable: false, writable: false, configurable: true,
171
+ });
172
+ }
173
+ return payload;
174
+ }
138
175
  function serverlessRead(value, valid) {
139
176
  const body = value && typeof value === 'object' && !Array.isArray(value) ? value : null;
140
177
  if (!body || !valid(body)) {
@@ -336,7 +373,7 @@ export class Inference {
336
373
  this.key = config.inferenceKey || envInferenceKey() || envApiKey();
337
374
  // Same default host as HttpClient (api.runbios.ai); api.runbios.ai cutover
338
375
  // is planned once its DNS exists — update both call sites together.
339
- this.baseUrl = (config.baseUrl || envBaseUrl() || 'https://api.runbios.ai').replace(/\/+$/, '');
376
+ this.baseUrl = (config.baseUrl || envBaseUrl() || 'https://api-dev.runbios.ai').replace(/\/+$/, '');
340
377
  this.timeout = config.timeout ?? 900_000;
341
378
  this._http = http;
342
379
  }
@@ -792,7 +829,7 @@ export class Inference {
792
829
  // OpenAI-compatible key-scoped inference — `/v1/chat/completions`
793
830
  // --------------------------------------------------------------------------
794
831
  prepare(params, stream) {
795
- const { messages, model, tools, toolChoice, reasoningEffort, inferenceKey, idempotencyKey, requestId, signal, ...extra } = params;
832
+ const { messages, model, tools, toolChoice, reasoningEffort, inferenceKey, idempotencyKey, requestId, signal, loop, ...extra } = params;
796
833
  const key = inferenceKey || this.key;
797
834
  if (!key)
798
835
  throw new Error('an inferenceKey is required');
@@ -814,6 +851,8 @@ export class Inference {
814
851
  };
815
852
  if (idempotencyKey)
816
853
  headers['Idempotency-Key'] = idempotencyKey;
854
+ if (loop !== undefined)
855
+ headers[LOOP_PIPELINE_HEADER] = loopPipelineHeader(loop);
817
856
  return { body, key, headers, signal };
818
857
  }
819
858
  abortContext(signal) {
@@ -831,7 +870,10 @@ export class Inference {
831
870
  };
832
871
  }
833
872
  prepareVerb(params, stream, anthropic) {
834
- const { inferenceKey, idempotencyKey, requestId, signal, anthropicVersion, stream: requestedStream, ...payload } = params;
873
+ const { inferenceKey, idempotencyKey, requestId, signal, anthropicVersion, stream: requestedStream, loop, ...payload } = params;
874
+ if (loop !== undefined && !anthropic) {
875
+ throw new Error('loop capture is offered on chatCompletions and messages only');
876
+ }
835
877
  if (requestedStream !== undefined)
836
878
  throw new Error('Choose the streaming method instead of setting stream in a request body');
837
879
  const key = inferenceKey || this.key;
@@ -851,6 +893,8 @@ export class Inference {
851
893
  }
852
894
  headers['Anthropic-Version'] = anthropicVersion || '2023-06-01';
853
895
  }
896
+ if (loop !== undefined)
897
+ headers[LOOP_PIPELINE_HEADER] = loopPipelineHeader(loop);
854
898
  return { body: stream ? { ...payload, stream: true } : payload, headers, signal };
855
899
  }
856
900
  async sendVerb(path, params, anthropic = false) {
@@ -862,6 +906,8 @@ export class Inference {
862
906
  });
863
907
  if (!response.ok)
864
908
  throw await apiError(response);
909
+ const loop = anthropic ? params.loop : undefined;
910
+ const sampleId = anthropic ? takeLoopSampleId(response, loop) : null;
865
911
  let payload;
866
912
  try {
867
913
  payload = await response.json();
@@ -872,7 +918,7 @@ export class Inference {
872
918
  if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
873
919
  throw new ApiError(response.status, { error: 'Inference endpoint returned no response object' });
874
920
  }
875
- return payload;
921
+ return anthropic ? withLoopSampleId(payload, sampleId) : payload;
876
922
  }
877
923
  catch (error) {
878
924
  if (abort.controller.signal.aborted && !(error instanceof ApiError)) {
@@ -929,6 +975,8 @@ export class Inference {
929
975
  if (!contentType.includes('text/event-stream')) {
930
976
  throw new ApiError(response.status, { error: `Expected text/event-stream, received ${contentType || 'no content type'}` });
931
977
  }
978
+ if (anthropic)
979
+ takeLoopSampleId(response, params.loop);
932
980
  if (!response.body)
933
981
  throw new ApiError(0, { error: 'Inference stream returned no body' });
934
982
  for await (const data of parseSSE(response.body)) {
@@ -976,12 +1024,20 @@ export class Inference {
976
1024
  rerank(params) {
977
1025
  return this.sendVerb('/v1/rerank', params);
978
1026
  }
1027
+ /**
1028
+ * Anthropic Messages (`/v1/messages`). With `loop: { pipeline }` the
1029
+ * exchange is recorded into those pipelines and the result carries
1030
+ * `loop_sample_id` (see {@link LoopCaptureOption}). For serverless models:
1031
+ * a dedicated deployment, a pipeline's `loop-<tag>` model included, serves
1032
+ * the OpenAI shape and refuses this with 400; call it with `chatCompletions`.
1033
+ */
979
1034
  messages(params) {
980
1035
  return this.sendVerb('/v1/messages', params, true);
981
1036
  }
982
1037
  async *streamCompletions(params) {
983
1038
  yield* this.streamVerb('/v1/completions', params);
984
1039
  }
1040
+ /** Anthropic Messages, streamed. With `loop`, `loop.onSampleId` hears the sample id before the first event. */
985
1041
  async *streamMessages(params) {
986
1042
  yield* this.streamVerb('/v1/messages', params, true);
987
1043
  }
@@ -994,6 +1050,12 @@ export class Inference {
994
1050
  throw new Error('RunBiOS: modelId is required to retrieve an inference model');
995
1051
  return asInferenceModel(await this.modelRead(`/v1/models/${encodeURIComponent(id)}`, inferenceKey));
996
1052
  }
1053
+ /**
1054
+ * OpenAI chat completions (`/v1/chat/completions`). With
1055
+ * `loop: { pipeline: 'support-replies' }` the exchange is recorded into
1056
+ * that pipeline and the result carries `loop_sample_id`, the id to send
1057
+ * feedback on (see {@link LoopCaptureOption}).
1058
+ */
997
1059
  async chatCompletions(params) {
998
1060
  const prepared = this.prepare(params, false);
999
1061
  const abort = this.abortContext(prepared.signal);
@@ -1003,7 +1065,8 @@ export class Inference {
1003
1065
  });
1004
1066
  if (!response.ok)
1005
1067
  throw await apiError(response);
1006
- return await response.json();
1068
+ const sampleId = takeLoopSampleId(response, params.loop);
1069
+ return withLoopSampleId(await response.json(), sampleId);
1007
1070
  }
1008
1071
  catch (error) {
1009
1072
  if (abort.controller.signal.aborted && !(error instanceof ApiError)) {
@@ -1016,6 +1079,10 @@ export class Inference {
1016
1079
  abort.remove();
1017
1080
  }
1018
1081
  }
1082
+ /**
1083
+ * OpenAI chat completions, streamed. With `loop`, `loop.onSampleId` hears
1084
+ * the sample id as soon as the headers arrive, before the first chunk.
1085
+ */
1019
1086
  async *streamChatCompletions(params) {
1020
1087
  const prepared = this.prepare(params, true);
1021
1088
  const abort = this.abortContext(prepared.signal);
@@ -1029,6 +1096,7 @@ export class Inference {
1029
1096
  if (!contentType.includes('text/event-stream')) {
1030
1097
  throw new ApiError(response.status, { error: `Expected text/event-stream, received ${contentType || 'no content type'}` });
1031
1098
  }
1099
+ takeLoopSampleId(response, params.loop);
1032
1100
  if (!response.body)
1033
1101
  throw new ApiError(0, { error: 'Inference response has no body' });
1034
1102
  for await (const data of parseSSE(response.body)) {