runbios-sdk 0.2.22-dev.313 → 0.2.22-dev.319

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/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,53 @@ 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. It works for serverless models and dedicated
229
+ deployments, including the pipeline's own `loop-<tag>` model.
230
+
231
+ The gateway answers with the sample's id in `X-Loop-Sample-Id`. A non-streaming
232
+ result carries it as `loop_sample_id` (non-enumerable: it is never part of the
233
+ answer's JSON), and `loop.onSampleId` hears it as soon as the headers arrive,
234
+ before the first streamed chunk. It is `null` when the gateway recorded nothing.
235
+ Send feedback on that id; it is the trace id the feedback calls take.
236
+
237
+ ```typescript
238
+ const reply = await client.inference.chatCompletions({
239
+ model: 'loop-support-replies',
240
+ messages: [{ role: 'user', content: 'Where is my order?' }],
241
+ loop: { pipeline: 'support-replies' },
242
+ });
243
+ const sampleId = reply.loop_sample_id;
244
+ if (sampleId) {
245
+ await client.loop.upvote(sampleId); // a good answer
246
+ // await client.loop.downvote(sampleId); // a bad one
247
+ // await client.loop.correct(sampleId, 'The right answer.');
248
+ }
249
+
250
+ for await (const chunk of client.inference.streamChatCompletions({
251
+ model: 'loop-support-replies',
252
+ messages: [{ role: 'user', content: 'Where is my order?' }],
253
+ loop: { pipeline: 'support-replies', onSampleId: (id) => console.log('sample', id) },
254
+ })) {
255
+ process.stdout.write(String((chunk.choices as any)?.[0]?.delta?.content ?? ''));
256
+ }
257
+ ```
258
+
259
+ Feedback sent before the sample is recorded is refused `409
260
+ SAMPLE_NOT_YET_RECORDED`, and nothing is stored. `upvote`, `downvote`,
261
+ `correct`, `gold` and `signal` send it again after the `Retry-After` seconds (2
262
+ when none is given), at most 6 times in all, then throw that error. A stream
263
+ that was cut short is never recorded, so its id stays unknown. If you send
264
+ `X-Request-ID`, keep it unique per call: a repeated one is recorded once.
265
+ `completions`, `embeddings` and `rerank` are not recorded and refuse the option.
266
+
220
267
  #### Workspace serverless usage and limits (read-only)
221
268
 
222
269
  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
@@ -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-dev.313";
39
+ export declare const VERSION = "0.2.22-dev.319";
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, 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, 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-dev.313';
39
+ export const VERSION = '0.2.22-dev.319';
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,50 @@ 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 for serverless models and dedicated deployments, on
70
+ * `/v1/chat/completions` and `/v1/messages` only.
71
+ */
72
+ export interface LoopCaptureOption {
73
+ /** One pipeline tag, or several; each a non-empty tag with no comma. */
74
+ pipeline: string | string[];
75
+ /**
76
+ * Called once with the sample id as soon as the response headers arrive
77
+ * (before the first streamed chunk), or with null when the gateway recorded
78
+ * nothing for this exchange (no `X-Loop-Sample-Id` came back). A
79
+ * non-streaming result also carries it as `loop_sample_id`.
80
+ */
81
+ onSampleId?: (sampleId: string | null) => void;
82
+ }
83
+ /** The request header the `loop` option sends. */
84
+ export declare const LOOP_PIPELINE_HEADER = "X-Loop-Pipeline";
85
+ /** The response header the gateway answers a recorded exchange with: the sample's id. */
86
+ export declare const LOOP_SAMPLE_ID_HEADER = "X-Loop-Sample-Id";
87
+ /**
88
+ * A non-streaming `/v1` answer, as the endpoint sent it, plus
89
+ * `loop_sample_id`: the `X-Loop-Sample-Id` the gateway answered with, or null
90
+ * when it sent none. It is NON-ENUMERABLE, so it is never part of the
91
+ * object's JSON or its keys; read it by name.
92
+ */
93
+ export interface LoopSampleIdOnResult {
94
+ readonly loop_sample_id?: string | null;
95
+ }
52
96
  export interface ChatCompletionParams extends Record<string, unknown> {
53
97
  messages: ChatMessage[];
54
98
  model?: string;
@@ -75,8 +119,10 @@ export interface ChatCompletionParams extends Record<string, unknown> {
75
119
  idempotencyKey?: string;
76
120
  requestId?: string;
77
121
  signal?: AbortSignal;
122
+ /** Record this exchange into Conscious Loop pipelines. See {@link LoopCaptureOption}. */
123
+ loop?: LoopCaptureOption;
78
124
  }
79
- export type ChatCompletionResponse = Record<string, unknown>;
125
+ export type ChatCompletionResponse = Record<string, unknown> & LoopSampleIdOnResult;
80
126
  export type ChatCompletionChunk = Record<string, unknown>;
81
127
  export interface InferenceVerbParams extends Record<string, unknown> {
82
128
  model?: string;
@@ -99,7 +145,11 @@ export interface AnthropicMessageParams extends InferenceVerbParams {
99
145
  messages: Array<Record<string, unknown>>;
100
146
  max_tokens: number;
101
147
  anthropicVersion?: string;
148
+ /** Record this exchange into Conscious Loop pipelines. See {@link LoopCaptureOption}. */
149
+ loop?: LoopCaptureOption;
102
150
  }
151
+ /** A non-streaming `/v1/messages` answer, with `loop_sample_id` (see {@link LoopSampleIdOnResult}). */
152
+ export type AnthropicMessageResponse = Record<string, unknown> & LoopSampleIdOnResult;
103
153
  export type ServerlessUsageWindow = '1h' | '24h' | '7d' | '30d' | '90d';
104
154
  export type ServerlessTimeseriesMetric = 'requests' | 'tokens' | 'spend' | 'ttft_p50' | 'ttft_p95' | 'tps';
105
155
  export interface ServerlessWorkspaceLimits {
@@ -359,12 +409,28 @@ export declare class Inference {
359
409
  completions(params: CompletionParams): Promise<Record<string, unknown>>;
360
410
  embeddings(params: EmbeddingParams): Promise<Record<string, unknown>>;
361
411
  rerank(params: RerankParams): Promise<Record<string, unknown>>;
362
- messages(params: AnthropicMessageParams): Promise<Record<string, unknown>>;
412
+ /**
413
+ * Anthropic Messages (`/v1/messages`). With `loop: { pipeline }` the
414
+ * exchange is recorded into those pipelines and the result carries
415
+ * `loop_sample_id` (see {@link LoopCaptureOption}).
416
+ */
417
+ messages(params: AnthropicMessageParams): Promise<AnthropicMessageResponse>;
363
418
  streamCompletions(params: CompletionParams): AsyncGenerator<Record<string, unknown>>;
419
+ /** Anthropic Messages, streamed. With `loop`, `loop.onSampleId` hears the sample id before the first event. */
364
420
  streamMessages(params: AnthropicMessageParams): AsyncGenerator<Record<string, unknown>>;
365
421
  listModels(inferenceKey?: string): Promise<InferenceModelListResponse>;
366
422
  retrieveModel(modelId: string, inferenceKey?: string): Promise<InferenceModel>;
423
+ /**
424
+ * OpenAI chat completions (`/v1/chat/completions`). With
425
+ * `loop: { pipeline: 'support-replies' }` the exchange is recorded into
426
+ * that pipeline and the result carries `loop_sample_id`, the id to send
427
+ * feedback on (see {@link LoopCaptureOption}).
428
+ */
367
429
  chatCompletions(params: ChatCompletionParams): Promise<ChatCompletionResponse>;
430
+ /**
431
+ * OpenAI chat completions, streamed. With `loop`, `loop.onSampleId` hears
432
+ * the sample id as soon as the headers arrive, before the first chunk.
433
+ */
368
434
  streamChatCompletions(params: ChatCompletionParams): AsyncGenerator<ChatCompletionChunk>;
369
435
  }
370
436
  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)) {
@@ -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,18 @@ 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}).
1031
+ */
979
1032
  messages(params) {
980
1033
  return this.sendVerb('/v1/messages', params, true);
981
1034
  }
982
1035
  async *streamCompletions(params) {
983
1036
  yield* this.streamVerb('/v1/completions', params);
984
1037
  }
1038
+ /** Anthropic Messages, streamed. With `loop`, `loop.onSampleId` hears the sample id before the first event. */
985
1039
  async *streamMessages(params) {
986
1040
  yield* this.streamVerb('/v1/messages', params, true);
987
1041
  }
@@ -994,6 +1048,12 @@ export class Inference {
994
1048
  throw new Error('RunBiOS: modelId is required to retrieve an inference model');
995
1049
  return asInferenceModel(await this.modelRead(`/v1/models/${encodeURIComponent(id)}`, inferenceKey));
996
1050
  }
1051
+ /**
1052
+ * OpenAI chat completions (`/v1/chat/completions`). With
1053
+ * `loop: { pipeline: 'support-replies' }` the exchange is recorded into
1054
+ * that pipeline and the result carries `loop_sample_id`, the id to send
1055
+ * feedback on (see {@link LoopCaptureOption}).
1056
+ */
997
1057
  async chatCompletions(params) {
998
1058
  const prepared = this.prepare(params, false);
999
1059
  const abort = this.abortContext(prepared.signal);
@@ -1003,7 +1063,8 @@ export class Inference {
1003
1063
  });
1004
1064
  if (!response.ok)
1005
1065
  throw await apiError(response);
1006
- return await response.json();
1066
+ const sampleId = takeLoopSampleId(response, params.loop);
1067
+ return withLoopSampleId(await response.json(), sampleId);
1007
1068
  }
1008
1069
  catch (error) {
1009
1070
  if (abort.controller.signal.aborted && !(error instanceof ApiError)) {
@@ -1016,6 +1077,10 @@ export class Inference {
1016
1077
  abort.remove();
1017
1078
  }
1018
1079
  }
1080
+ /**
1081
+ * OpenAI chat completions, streamed. With `loop`, `loop.onSampleId` hears
1082
+ * the sample id as soon as the headers arrive, before the first chunk.
1083
+ */
1019
1084
  async *streamChatCompletions(params) {
1020
1085
  const prepared = this.prepare(params, true);
1021
1086
  const abort = this.abortContext(prepared.signal);
@@ -1029,6 +1094,7 @@ export class Inference {
1029
1094
  if (!contentType.includes('text/event-stream')) {
1030
1095
  throw new ApiError(response.status, { error: `Expected text/event-stream, received ${contentType || 'no content type'}` });
1031
1096
  }
1097
+ takeLoopSampleId(response, params.loop);
1032
1098
  if (!response.body)
1033
1099
  throw new ApiError(0, { error: 'Inference response has no body' });
1034
1100
  for await (const data of parseSSE(response.body)) {
@@ -1,63 +1,11 @@
1
- import type { HttpClient } from '../client.js';
2
- import type { LoopTrace, LoopTraceDeleteResponse, LoopTraceListResponse, LoopTraceListParams, LoopCaptureParams, LoopCaptureResult, LoopImportParams, LoopImportResult, LoopSignal, LoopSignalParams, LoopLabel, LoopLabelCount, LoopConfig, LoopConfigParams, LoopStats, LoopTracePipelinesResponse, LoopMetricsHistory, LoopMetricsHistoryParams, TrainingRuleDeleteResponse, TrainingRunListParams, TrainingRunListResponse, TrainingRunResponse, TrainingRunActionResponse, TrainingRunPromoteRequest, TrainingRunRejectRequest, TrainingRunRollbackRequest, TrainingRunCancelRequest, Evaluation, EvaluationItemListParams, EvaluationItemsResponse, Benchmark, BenchmarkCreateParams, BenchmarkHistoryParams, BenchmarkHistoryResponse, BenchmarkItemListParams, BenchmarkItemsResponse, BenchmarkListParams, BenchmarkListResponse, BenchmarkRetireParams, BenchmarkRun, BenchmarkRunItemListParams, BenchmarkRunItemsResponse, Pipeline, PipelineListParams, PipelineListResponse, PipelineWriteRequest, PipelineMutationResponse, LoopModelsResponse, PipelineImportParams, PipelineBenchmarksResponse, PipelineBenchmarkAttachParams, PipelineBenchmarkMoveParams, VersionCompareResponse, VersionCompareParams, AttemptCompareResponse, AttemptCompareParams } from '../types.js';
3
- /**
4
- * The Conscious Loop -- pipelines that keep improving a model from the
5
- * conversations you send them.
6
- *
7
- * A pipeline is one use case: a model, the tags whose samples train it, when
8
- * to train, and how a new version is promoted. Every time it trains it makes
9
- * an attempt; an attempt that goes live becomes the next version: one that
10
- * beats the live model, one a member approves, or, after a deleted
11
- * conversation, a rebuild without it that is not clearly worse (a `manual`
12
- * pipeline waits for a member even then).
13
- *
14
- * Nothing is captured until a source is switched on, and you can switch it
15
- * off again at any time. Creating a pipeline switches on the source named by
16
- * its own tag (`own_tag`), stamping that tag, and deleting it switches that
17
- * source off again. Anything that looks like a credential or a personal detail
18
- * is removed before the record is written, never afterwards.
19
- *
20
- * Requires an API key carrying `loop:read` and/or `loop:write`. Those scopes are
21
- * deliberately absent from the Read Only preset, because what they return is
22
- * your raw prompts and completions rather than catalog metadata.
23
- *
24
- * Offered on the development environment only for now. Anywhere else every
25
- * method here, reads included, rejects with a `ComingSoonError` whose
26
- * `code` is `LOOP_COMING_SOON` (HTTP 403): a deliberate product state, not an
27
- * outage, so retrying cannot succeed there.
28
- *
29
- * @example
30
- * ```ts
31
- * // 1. A pipeline: a model it can train, and its own tag.
32
- * const { models } = await client.loop.listModels();
33
- * const { pipeline } = await client.loop.createPipeline({
34
- * name: 'Support replies',
35
- * model: { id: models[0].id },
36
- * });
37
- *
38
- * // 2. Send samples from your app to the pipeline's own source: its create
39
- * // switched that source on, and it tags everything it records.
40
- * const sent = await client.loop.capture({
41
- * deployment_id: pipeline.own_tag!,
42
- * model: 'gpt-4o',
43
- * messages: [{ role: 'user', content: 'what is our refund window' }],
44
- * completion: 'thirty days',
45
- * });
46
- *
47
- * // 3. Say whether it was right, on the trace_id the capture answered with.
48
- * // A correction is the most valuable answer.
49
- * if (sent.trace_id) {
50
- * await client.loop.correct(sent.trace_id, 'Thirty days from delivery, no questions asked.');
51
- * }
52
- *
53
- * // 4. Once it has its minimum, it trains; versions appear on the pipeline.
54
- * const p = await client.loop.getPipeline(pipeline.rule_id);
55
- * ```
56
- */
1
+ import { type HttpClient } from '../client.js';
2
+ import type { LoopTrace, LoopTraceDeleteResponse, LoopTraceListResponse, LoopTraceListParams, LoopCaptureParams, LoopCaptureResult, LoopImportParams, LoopImportResult, LoopSignal, LoopSignalParams, LoopLabel, LoopLabelCount, LoopConfig, LoopConfigParams, LoopStats, LoopTracePipelinesResponse, LoopMetricsHistory, LoopMetricsHistoryParams, TrainingRuleDeleteResponse, TrainingRunListParams, TrainingRunListResponse, TrainingRunResponse, TrainingRunActionResponse, TrainingRunPromoteRequest, TrainingRunRejectRequest, TrainingRunRollbackRequest, TrainingRunCancelRequest, Evaluation, EvaluationItemListParams, EvaluationItemsResponse, Benchmark, BenchmarkCreateParams, BenchmarkHistoryParams, BenchmarkHistoryResponse, BenchmarkItemListParams, BenchmarkItemsResponse, BenchmarkListParams, BenchmarkListResponse, BenchmarkRetireParams, BenchmarkRun, BenchmarkRunItemListParams, BenchmarkRunItemsResponse, Pipeline, PipelineAttemptListParams, PipelineAttemptListResponse, PipelineListParams, PipelineListResponse, PipelineWriteRequest, PipelineMutationResponse, LoopModelsResponse, PipelineImportParams, PipelineBenchmarksResponse, PipelineBenchmarkAttachParams, PipelineBenchmarkMoveParams, VersionCompareResponse, VersionCompareParams, AttemptCompareResponse, AttemptCompareParams } from '../types.js';
57
3
  export declare class Loop {
58
4
  private readonly _http;
59
5
  /** @internal */
60
6
  constructor(_http: HttpClient);
7
+ /** @internal How feedback waits between tries; tests replace it so nothing sleeps. */
8
+ _sleep: (ms: number) => Promise<void>;
61
9
  /**
62
10
  * Send one exchange to be captured.
63
11
  *
@@ -139,7 +87,14 @@ export declare class Loop {
139
87
  * a shorter list moves every row after the gap and imports it again.
140
88
  */
141
89
  importRows(params: LoopImportParams): Promise<LoopImportResult>;
142
- /** List captured conversations, newest first. */
90
+ /**
91
+ * List captured conversations, newest first.
92
+ *
93
+ * `q` searches the text: 2 to 200 characters, any case, matched as written
94
+ * in every message, the answer and its tool calls. Samples kept in your own
95
+ * storage cannot be searched; a search answers how many were left out as
96
+ * `unsearched`.
97
+ */
143
98
  listTraces(params?: LoopTraceListParams): Promise<LoopTraceListResponse>;
144
99
  /**
145
100
  * Read one conversation, with every verdict and tag recorded on it.
@@ -176,6 +131,13 @@ export declare class Loop {
176
131
  * `edited` or `gold` verdict is the example, in place of the model's answer;
177
132
  * a `rejected` answer is left out. Nothing is learned from an answer that was
178
133
  * rejected or replaced. `source` defaults to `human`.
134
+ *
135
+ * Feedback on an `X-Loop-Sample-Id` sent right after the answer can reach
136
+ * the service before the sample does, which answers 409
137
+ * `SAMPLE_NOT_YET_RECORDED` and stores nothing. This method, and so `upvote`,
138
+ * `downvote`, `correct` and `gold`, sends it again after the `Retry-After`
139
+ * seconds (2 when none is given), at most 6 times in all, and then throws
140
+ * that error. No other error is retried.
179
141
  */
180
142
  signal(traceId: string, params: LoopSignalParams): Promise<LoopSignal>;
181
143
  /**
@@ -356,15 +318,21 @@ export declare class Loop {
356
318
  */
357
319
  cancelTrainingRun(id: string, params?: TrainingRunCancelRequest): Promise<TrainingRunActionResponse>;
358
320
  /**
359
- * Every pipeline in the workspace: its versions (the attempts that became
360
- * the live model), every attempt, which version is live, the attempt in
361
- * flight, and what the pipeline is waiting for.
321
+ * The workspace's pipelines, a page at a time: its versions (the attempts
322
+ * that became the live model), its latest 5 attempts (`attempts_total` says
323
+ * how many; {@link listPipelineAttempts} pages through every one), which
324
+ * version is live, the attempt in flight, and what the pipeline is waiting
325
+ * for.
326
+ *
327
+ * `limit` is 100 when absent and at most 100; `q` matches the name, the tag
328
+ * or the model (case-insensitive); `status` keeps the pipelines `training`,
329
+ * waiting on you (`needs_you`), `serving` or `paused`.
362
330
  *
363
331
  * RETURNED WITH ITS ENVELOPE, because the list is not always all of them:
364
- * the service returns a page of 100, newest first, `total` is every
365
- * pipeline in the workspace and `truncated` is true when more exist after
366
- * this page. A caller that shows `pipelines` alone presents a short list as
367
- * the whole of it; the next page is `{ offset: offset + pipelines.length }`.
332
+ * the service returns one page, newest first, `total` is every pipeline
333
+ * that matches and `truncated` is true when more exist after this page. A
334
+ * caller that shows `pipelines` alone presents a short list as the whole of
335
+ * it; the next page is `{ offset: offset + pipelines.length }`.
368
336
  */
369
337
  listPipelines(params?: PipelineListParams): Promise<PipelineListResponse>;
370
338
  /**
@@ -378,6 +346,17 @@ export declare class Loop {
378
346
  * at most, not an exact spend.
379
347
  */
380
348
  getPipeline(id: string): Promise<Pipeline>;
349
+ /**
350
+ * A pipeline's attempts, newest first, a page at a time: every training run
351
+ * it made, winner or not, each the shape `attempts` has on the pipeline
352
+ * (with its `explanation` in plain words). `limit` is 50 when absent and at
353
+ * most 200. Narrow by `result` (`became_version`, `not_better`,
354
+ * `inconclusive`, `failed`, `stopped`, `running` -- which includes one
355
+ * waiting for a decision -- or `waiting_for_review`) and by `model` (`main`,
356
+ * or `second` for the second model's attempts); `total` follows the
357
+ * filters.
358
+ */
359
+ listPipelineAttempts(id: string, params?: PipelineAttemptListParams): Promise<PipelineAttemptListResponse>;
381
360
  /**
382
361
  * The base models a pipeline can train: only those the engine trains with
383
362
  * LoRA SFT and the platform can serve for the comparison, recommended first.
@@ -394,6 +373,9 @@ export declare class Loop {
394
373
  * not learned yet plus a replay of what it already learned (about half
395
374
  * of what it trains on, picked at random, so it is not forgotten), and a
396
375
  * winner is merged in and served as one model.
376
+ * `unavailable` lists every other model the platform knows, each with
377
+ * why it is not offered here (`reason_code`, and `reason`, one plain
378
+ * sentence); `[]` from a service from before it was sent.
397
379
  * When the list cannot be read it throws `ApiError` 503
398
380
  * `MODELS_UNAVAILABLE`, whose `body.floors` still carries the floors.
399
381
  */
@@ -413,7 +395,9 @@ export declare class Loop {
413
395
  * What is charged is the GPU time the runs actually use; the platform's own
414
396
  * per-run and monthly limits stop a run that goes wrong. `holdout.percent`
415
397
  * (5 to 20, default 5) is the share of its samples held back to judge each
416
- * attempt and never trained on.
398
+ * attempt and never trained on. `training` sets any of its training
399
+ * settings (`epochs`, `learning_rate`, `lora_rank`, `lora_alpha`,
400
+ * `replay_percent`); left out, or null, every one is automatic.
417
401
  */
418
402
  createPipeline(params: PipelineWriteRequest): Promise<PipelineMutationResponse>;
419
403
  /**
@@ -421,7 +405,13 @@ export declare class Loop {
421
405
  * the limit and `challenger_model: null` removes the challenger. `promotion`
422
406
  * is how the promotion policy is set. A new `holdout.percent` is a new
423
407
  * revision: lowering it keeps every sample already held back, and raising it
424
- * holds back only new samples. Saving is the authorization, as on a create.
408
+ * holds back only new samples. `training` changes only the settings it
409
+ * carries -- `{ epochs: 8 }` sets the passes and keeps the rest,
410
+ * `{ epochs: null }` puts the passes back to automatic -- and
411
+ * `training: null` puts every one back to automatic; the next attempt
412
+ * trains with them, and earlier attempts keep the settings they trained
413
+ * with. Out of range is 422 `TRAINING_SETTING_OUT_OF_RANGE`, naming the
414
+ * field. Saving is the authorization, as on a create.
425
415
  */
426
416
  updatePipeline(id: string, params: PipelineWriteRequest): Promise<PipelineMutationResponse>;
427
417
  /**
@@ -585,6 +575,9 @@ export declare class Loop {
585
575
  * series that silently dropped the runs where the benchmark could not be
586
576
  * scored would read as an unbroken line and not be one, so those points come
587
577
  * back with `candidate_score` null and `status_reason` saying why.
578
+ *
579
+ * `pipeline` (a pipeline's id) keeps only the points of that pipeline's
580
+ * attempts, so the trend is one pipeline's attempts by attempt number.
588
581
  */
589
582
  getBenchmarkHistory(id: string, params?: BenchmarkHistoryParams): Promise<BenchmarkHistoryResponse>;
590
583
  /**
@@ -1,3 +1,4 @@
1
+ import { ApiError } from '../client.js';
1
2
  /**
2
3
  * The Conscious Loop -- pipelines that keep improving a model from the
3
4
  * conversations you send them.
@@ -52,12 +53,25 @@
52
53
  * const p = await client.loop.getPipeline(pipeline.rule_id);
53
54
  * ```
54
55
  */
56
+ /**
57
+ * The code feedback is refused with (409, `Retry-After`) when it names a sample
58
+ * id the gateway issued moments ago (`X-Loop-Sample-Id`) and the sample is not
59
+ * recorded yet. Nothing was stored, so sending the same feedback again is safe.
60
+ */
61
+ const SAMPLE_NOT_YET_RECORDED = 'SAMPLE_NOT_YET_RECORDED';
62
+ /** Feedback is sent at most this many times while its sample is on its way. */
63
+ const FEEDBACK_TRIES = 6;
64
+ /** The wait when the 409 names none, and the longest wait honoured. */
65
+ const FEEDBACK_RETRY_DEFAULT_SECONDS = 2;
66
+ const FEEDBACK_RETRY_MAX_SECONDS = 30;
55
67
  export class Loop {
56
68
  _http;
57
69
  /** @internal */
58
70
  constructor(_http) {
59
71
  this._http = _http;
60
72
  }
73
+ /** @internal How feedback waits between tries; tests replace it so nothing sleeps. */
74
+ _sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
61
75
  // ── capture ───────────────────────────────────────────────────────────
62
76
  /**
63
77
  * Send one exchange to be captured.
@@ -145,9 +159,18 @@ export class Loop {
145
159
  return this._http.fetchPost('/api/loop/import', params);
146
160
  }
147
161
  // ── traces ────────────────────────────────────────────────────────────
148
- /** List captured conversations, newest first. */
162
+ /**
163
+ * List captured conversations, newest first.
164
+ *
165
+ * `q` searches the text: 2 to 200 characters, any case, matched as written
166
+ * in every message, the answer and its tool calls. Samples kept in your own
167
+ * storage cannot be searched; a search answers how many were left out as
168
+ * `unsearched`.
169
+ */
149
170
  async listTraces(params = {}) {
150
171
  const q = new URLSearchParams();
172
+ if (params.q)
173
+ q.set('q', params.q);
151
174
  if (params.deployment_id)
152
175
  q.set('deployment_id', params.deployment_id);
153
176
  if (params.conversation_id)
@@ -226,10 +249,29 @@ export class Loop {
226
249
  * `edited` or `gold` verdict is the example, in place of the model's answer;
227
250
  * a `rejected` answer is left out. Nothing is learned from an answer that was
228
251
  * rejected or replaced. `source` defaults to `human`.
252
+ *
253
+ * Feedback on an `X-Loop-Sample-Id` sent right after the answer can reach
254
+ * the service before the sample does, which answers 409
255
+ * `SAMPLE_NOT_YET_RECORDED` and stores nothing. This method, and so `upvote`,
256
+ * `downvote`, `correct` and `gold`, sends it again after the `Retry-After`
257
+ * seconds (2 when none is given), at most 6 times in all, and then throws
258
+ * that error. No other error is retried.
229
259
  */
230
260
  async signal(traceId, params) {
231
- const res = await this._http.fetchPost(`/api/loop/traces/${encodeURIComponent(traceId)}/signals`, params);
232
- return res.signal;
261
+ const path = `/api/loop/traces/${encodeURIComponent(traceId)}/signals`;
262
+ for (let tries = 1;; tries++) {
263
+ try {
264
+ const res = await this._http.fetchPost(path, params);
265
+ return res.signal;
266
+ }
267
+ catch (err) {
268
+ const onItsWay = err instanceof ApiError && err.status === 409 && err.code === SAMPLE_NOT_YET_RECORDED;
269
+ if (!onItsWay || tries >= FEEDBACK_TRIES)
270
+ throw err;
271
+ const seconds = Math.min(err.retryAfter ?? FEEDBACK_RETRY_DEFAULT_SECONDS, FEEDBACK_RETRY_MAX_SECONDS);
272
+ await this._sleep(seconds * 1000);
273
+ }
274
+ }
233
275
  }
234
276
  /**
235
277
  * Thumbs up: this answer was good. Shorthand for `signal(id, { verdict:
@@ -481,20 +523,32 @@ export class Loop {
481
523
  }
482
524
  // ── pipelines ─────────────────────────────────────────────────────────
483
525
  /**
484
- * Every pipeline in the workspace: its versions (the attempts that became
485
- * the live model), every attempt, which version is live, the attempt in
486
- * flight, and what the pipeline is waiting for.
526
+ * The workspace's pipelines, a page at a time: its versions (the attempts
527
+ * that became the live model), its latest 5 attempts (`attempts_total` says
528
+ * how many; {@link listPipelineAttempts} pages through every one), which
529
+ * version is live, the attempt in flight, and what the pipeline is waiting
530
+ * for.
531
+ *
532
+ * `limit` is 100 when absent and at most 100; `q` matches the name, the tag
533
+ * or the model (case-insensitive); `status` keeps the pipelines `training`,
534
+ * waiting on you (`needs_you`), `serving` or `paused`.
487
535
  *
488
536
  * RETURNED WITH ITS ENVELOPE, because the list is not always all of them:
489
- * the service returns a page of 100, newest first, `total` is every
490
- * pipeline in the workspace and `truncated` is true when more exist after
491
- * this page. A caller that shows `pipelines` alone presents a short list as
492
- * the whole of it; the next page is `{ offset: offset + pipelines.length }`.
537
+ * the service returns one page, newest first, `total` is every pipeline
538
+ * that matches and `truncated` is true when more exist after this page. A
539
+ * caller that shows `pipelines` alone presents a short list as the whole of
540
+ * it; the next page is `{ offset: offset + pipelines.length }`.
493
541
  */
494
542
  async listPipelines(params = {}) {
495
543
  const q = new URLSearchParams();
544
+ if (params.limit != null)
545
+ q.set('limit', String(params.limit));
496
546
  if (params.offset)
497
547
  q.set('offset', String(params.offset));
548
+ if (params.q)
549
+ q.set('q', params.q);
550
+ if (params.status)
551
+ q.set('status', params.status);
498
552
  const qs = q.toString();
499
553
  const res = await this._http.fetchGet(`/api/loop/pipelines${qs ? `?${qs}` : ''}`);
500
554
  const pipelines = res.pipelines || [];
@@ -519,6 +573,31 @@ export class Loop {
519
573
  const res = await this._http.fetchGet(`/api/loop/pipelines/${encodeURIComponent(id)}`);
520
574
  return res.pipeline;
521
575
  }
576
+ /**
577
+ * A pipeline's attempts, newest first, a page at a time: every training run
578
+ * it made, winner or not, each the shape `attempts` has on the pipeline
579
+ * (with its `explanation` in plain words). `limit` is 50 when absent and at
580
+ * most 200. Narrow by `result` (`became_version`, `not_better`,
581
+ * `inconclusive`, `failed`, `stopped`, `running` -- which includes one
582
+ * waiting for a decision -- or `waiting_for_review`) and by `model` (`main`,
583
+ * or `second` for the second model's attempts); `total` follows the
584
+ * filters.
585
+ */
586
+ async listPipelineAttempts(id, params = {}) {
587
+ const q = new URLSearchParams();
588
+ if (params.limit != null)
589
+ q.set('limit', String(params.limit));
590
+ if (params.offset)
591
+ q.set('offset', String(params.offset));
592
+ if (params.result)
593
+ q.set('result', params.result);
594
+ if (params.model)
595
+ q.set('model', params.model);
596
+ const qs = q.toString();
597
+ const res = await this._http.fetchGet(`/api/loop/pipelines/${encodeURIComponent(id)}/attempts${qs ? `?${qs}` : ''}`);
598
+ const attempts = res.attempts || [];
599
+ return { attempts, total: typeof res.total === 'number' ? res.total : attempts.length };
600
+ }
522
601
  // ── creating and changing pipelines ───────────────────────────────────
523
602
  //
524
603
  // One body -- a name, a model, tags, when to train, how to promote -- and
@@ -541,12 +620,15 @@ export class Loop {
541
620
  * not learned yet plus a replay of what it already learned (about half
542
621
  * of what it trains on, picked at random, so it is not forgotten), and a
543
622
  * winner is merged in and served as one model.
623
+ * `unavailable` lists every other model the platform knows, each with
624
+ * why it is not offered here (`reason_code`, and `reason`, one plain
625
+ * sentence); `[]` from a service from before it was sent.
544
626
  * When the list cannot be read it throws `ApiError` 503
545
627
  * `MODELS_UNAVAILABLE`, whose `body.floors` still carries the floors.
546
628
  */
547
629
  async listModels() {
548
630
  const res = await this._http.fetchGet('/api/loop/models');
549
- return { ...res, models: res.models || [] };
631
+ return { ...res, models: res.models || [], unavailable: res.unavailable || [] };
550
632
  }
551
633
  /**
552
634
  * Create a pipeline -- one use case.
@@ -563,7 +645,9 @@ export class Loop {
563
645
  * What is charged is the GPU time the runs actually use; the platform's own
564
646
  * per-run and monthly limits stop a run that goes wrong. `holdout.percent`
565
647
  * (5 to 20, default 5) is the share of its samples held back to judge each
566
- * attempt and never trained on.
648
+ * attempt and never trained on. `training` sets any of its training
649
+ * settings (`epochs`, `learning_rate`, `lora_rank`, `lora_alpha`,
650
+ * `replay_percent`); left out, or null, every one is automatic.
567
651
  */
568
652
  async createPipeline(params) {
569
653
  return this._http.fetchPost('/api/loop/pipelines', params);
@@ -573,7 +657,13 @@ export class Loop {
573
657
  * the limit and `challenger_model: null` removes the challenger. `promotion`
574
658
  * is how the promotion policy is set. A new `holdout.percent` is a new
575
659
  * revision: lowering it keeps every sample already held back, and raising it
576
- * holds back only new samples. Saving is the authorization, as on a create.
660
+ * holds back only new samples. `training` changes only the settings it
661
+ * carries -- `{ epochs: 8 }` sets the passes and keeps the rest,
662
+ * `{ epochs: null }` puts the passes back to automatic -- and
663
+ * `training: null` puts every one back to automatic; the next attempt
664
+ * trains with them, and earlier attempts keep the settings they trained
665
+ * with. Out of range is 422 `TRAINING_SETTING_OUT_OF_RANGE`, naming the
666
+ * field. Saving is the authorization, as on a create.
577
667
  */
578
668
  async updatePipeline(id, params) {
579
669
  return this._http.fetchPut(`/api/loop/pipelines/${encodeURIComponent(id)}`, params);
@@ -811,9 +901,14 @@ export class Loop {
811
901
  * series that silently dropped the runs where the benchmark could not be
812
902
  * scored would read as an unbroken line and not be one, so those points come
813
903
  * back with `candidate_score` null and `status_reason` saying why.
904
+ *
905
+ * `pipeline` (a pipeline's id) keeps only the points of that pipeline's
906
+ * attempts, so the trend is one pipeline's attempts by attempt number.
814
907
  */
815
908
  async getBenchmarkHistory(id, params = {}) {
816
909
  const q = new URLSearchParams();
910
+ if (params.pipeline)
911
+ q.set('pipeline', params.pipeline);
817
912
  if (params.limit != null)
818
913
  q.set('limit', String(params.limit));
819
914
  if (params.offset != null)
package/dist/types.d.ts CHANGED
@@ -2771,12 +2771,23 @@ export interface LoopTraceListParams {
2771
2771
  signal?: 'good' | 'bad' | 'corrected';
2772
2772
  /** Only the conversations nobody has given feedback on yet. */
2773
2773
  unsignalled?: boolean;
2774
+ /**
2775
+ * Text search: 2 to 200 characters, case-insensitive, matched as written
2776
+ * (`%` and `_` are plain characters) in every message, the answer and the
2777
+ * answer's tool calls. Combines with every other filter.
2778
+ */
2779
+ q?: string;
2774
2780
  }
2775
2781
  export interface LoopTraceListResponse {
2776
2782
  traces: LoopTrace[];
2777
2783
  total: number;
2778
2784
  limit: number;
2779
2785
  offset: number;
2786
+ /**
2787
+ * Present only when `q` was sent: how many of the samples the other filters
2788
+ * select keep their text in your own storage and so were not searched.
2789
+ */
2790
+ unsearched?: number;
2780
2791
  }
2781
2792
  export interface LoopConfig {
2782
2793
  deployment_id: string;
@@ -2995,8 +3006,9 @@ export type TrainingTrigger = 'rows' | 'cadence' | 'both' | 'manual' | 'variant'
2995
3006
  * `served_as` is `adapter` for an attempt from the base and `merged` for one
2996
3007
  * built on versions (one model with every version merged in). A winner is
2997
3008
  * merged in and served as one model. `learned_deleted_conversation` is true
2998
- * when a conversation it trained on has since been deleted: a rebuild
2999
- * without it is on its way, and it can no longer be rolled back to.
3009
+ * when it -- or a version it was built on, whose model is merged into it --
3010
+ * learned a conversation that has since been deleted: a rebuild without it is
3011
+ * on its way, and it can no longer be rolled back to.
3000
3012
  */
3001
3013
  export interface Lineage {
3002
3014
  trained_from: 'base' | 'versions';
@@ -3023,6 +3035,12 @@ export interface TrainingRecipe {
3023
3035
  num_train_epochs: number | null;
3024
3036
  lora_rank: number | null;
3025
3037
  lora_alpha: number | null;
3038
+ /**
3039
+ * True when one of the pipeline's own training settings ({@link Pipeline.training})
3040
+ * was applied over the automatic recipe for this run, frozen when it started;
3041
+ * false when it trained on automatic settings throughout.
3042
+ */
3043
+ custom: boolean;
3026
3044
  }
3027
3045
  export type TrainingRunState = 'built' | 'registering' | 'dataset_validating' | 'preflighting' | 'job_creating' | 'training' | 'checkpoint_ready' | 'candidate_booking' | 'candidate_running' | 'evaluating' | 'reported' | 'awaiting_review' | 'promoting' | 'waiting_funds'
3028
3046
  /**
@@ -3926,6 +3944,8 @@ export interface BenchmarkItemListParams {
3926
3944
  offset?: number;
3927
3945
  }
3928
3946
  export interface BenchmarkHistoryParams {
3947
+ /** Only the points of this pipeline's attempts (its id, `rule_id`). */
3948
+ pipeline?: string;
3929
3949
  /** 50 when absent; at most 200. */
3930
3950
  limit?: number;
3931
3951
  offset?: number;
@@ -4012,6 +4032,8 @@ export type TrainingRuleRunRefusalCode = 'NOT_ENOUGH_SAMPLES' | 'PIPELINE_NEEDS_
4012
4032
  * pipeline's current one (ignoring case) is kept, and whether it can still
4013
4033
  * be trained is checked when an attempt starts.
4014
4034
  * - `NO_GPU_FITS` (422): no machines can train or serve the model right now.
4035
+ * - `TRAINING_SETTING_OUT_OF_RANGE` (422): a `training` setting is outside its
4036
+ * range; the message names the field and the range. Nothing was saved.
4015
4037
  * - `MODEL_REVISION_UNAVAILABLE` (422): the model could not be pinned to an
4016
4038
  * exact revision because the training service did not answer. Try again
4017
4039
  * once it does.
@@ -4038,7 +4060,7 @@ export type TrainingRuleRunRefusalCode = 'NOT_ENOUGH_SAMPLES' | 'PIPELINE_NEEDS_
4038
4060
  * the key every comparison is scored with) did not answer just then. Try
4039
4061
  * again in a minute.
4040
4062
  */
4041
- export type TrainingRuleSaveRefusalCode = 'PIPELINE_NAME_TAKEN' | 'OWN_TAG_TAKEN' | 'REVISION_MISMATCH' | 'RUN_ACTIVE' | 'NOT_A_SIMPLE_PIPELINE' | 'MODEL_NOT_TRAINABLE' | 'NO_GPU_FITS' | 'MODEL_REVISION_UNAVAILABLE' | 'NO_JUDGE_MODEL' | 'PROMOTION_POLICY_UNKNOWN' | 'PROMOTION_K_REQUIRED' | 'PROMOTION_K_UNREACHABLE' | 'PROMOTION_POLICY_NEEDS_BENCHMARK' | 'TRAINING_NOT_ENABLED' | 'METHOD_NOT_ENABLED' | 'CEILING_TOO_LOW' | 'MODELS_UNAVAILABLE' | 'GPU_OPTIONS_UNAVAILABLE' | 'AGENT_COULD_NOT_START';
4063
+ export type TrainingRuleSaveRefusalCode = 'PIPELINE_NAME_TAKEN' | 'OWN_TAG_TAKEN' | 'REVISION_MISMATCH' | 'RUN_ACTIVE' | 'NOT_A_SIMPLE_PIPELINE' | 'MODEL_NOT_TRAINABLE' | 'NO_GPU_FITS' | 'TRAINING_SETTING_OUT_OF_RANGE' | 'MODEL_REVISION_UNAVAILABLE' | 'NO_JUDGE_MODEL' | 'PROMOTION_POLICY_UNKNOWN' | 'PROMOTION_K_REQUIRED' | 'PROMOTION_K_UNREACHABLE' | 'PROMOTION_POLICY_NEEDS_BENCHMARK' | 'TRAINING_NOT_ENABLED' | 'METHOD_NOT_ENABLED' | 'CEILING_TOO_LOW' | 'MODELS_UNAVAILABLE' | 'GPU_OPTIONS_UNAVAILABLE' | 'AGENT_COULD_NOT_START';
4042
4064
  /**
4043
4065
  * The body of a manual run refused `409 MONTHLY_LIMIT_REACHED`, as
4044
4066
  * `ApiError.body` carries it.
@@ -4271,6 +4293,69 @@ export interface PipelineAttempt {
4271
4293
  evaluation_id: string | null;
4272
4294
  /** Its last sentence. */
4273
4295
  reason: string | null;
4296
+ /**
4297
+ * What became of it in plain words, written by the service from its state,
4298
+ * verdict and error. See {@link AttemptExplanation}.
4299
+ */
4300
+ explanation: AttemptExplanation | null;
4301
+ }
4302
+ /**
4303
+ * An attempt's outcome in plain words: `headline` ("Could not be tested"),
4304
+ * `detail` (what happened and why) and `next` (what happens now; null when
4305
+ * there is nothing to say).
4306
+ */
4307
+ export interface AttemptExplanation {
4308
+ headline: string;
4309
+ detail: string;
4310
+ next: string | null;
4311
+ }
4312
+ /**
4313
+ * A pipeline's own training settings, each null while it is automatic (the
4314
+ * platform's value for its model, {@link PipelineTrainingDefaults}). A change
4315
+ * applies from the next attempt, and every attempt keeps the settings it
4316
+ * trained with on its `recipe` (`custom` true when one of these applied).
4317
+ * LoRA only: there is no full fine-tune, so the rank and alpha always apply.
4318
+ */
4319
+ export interface PipelineTrainingSettings {
4320
+ /** Passes over the data, 1 to 15. */
4321
+ epochs: number | null;
4322
+ /** 1e-6 to 1e-2. */
4323
+ learning_rate: number | null;
4324
+ /** 4, 8, 16, 32 or 64. */
4325
+ lora_rank: number | null;
4326
+ /** 1 to 512. */
4327
+ lora_alpha: number | null;
4328
+ /**
4329
+ * 0 to 50: the share of already-learned samples an attempt replays when it
4330
+ * builds on the live version, so the new version does not forget them.
4331
+ */
4332
+ replay_percent: number | null;
4333
+ }
4334
+ /**
4335
+ * What automatic means for this pipeline's model: what an attempt trains with
4336
+ * where {@link PipelineTrainingSettings} says null. The automatic recipe picks
4337
+ * its passes from `min_epochs` to `max_epochs` (at least 8 for Gemma 4).
4338
+ */
4339
+ export interface PipelineTrainingDefaults {
4340
+ learning_rate: number;
4341
+ lora_rank: number;
4342
+ lora_alpha: number;
4343
+ replay_percent: number;
4344
+ min_epochs: number;
4345
+ max_epochs: number;
4346
+ }
4347
+ /**
4348
+ * The training settings a create or edit sends. Every key is three-state:
4349
+ * absent keeps the pipeline's, null puts it back to automatic, a number sets
4350
+ * it. Out of range is refused 422 `TRAINING_SETTING_OUT_OF_RANGE` naming the
4351
+ * field and its range.
4352
+ */
4353
+ export interface PipelineTrainingRequest {
4354
+ epochs?: number | null;
4355
+ learning_rate?: number | null;
4356
+ lora_rank?: number | null;
4357
+ lora_alpha?: number | null;
4358
+ replay_percent?: number | null;
4274
4359
  }
4275
4360
  /** A model and the exact commit of it. */
4276
4361
  export interface PipelineModelRef {
@@ -4425,6 +4510,10 @@ export interface Pipeline {
4425
4510
  holdout: PipelineHoldout;
4426
4511
  /** Rows the next version holds back and never trains on. Detail only; null on the list. */
4427
4512
  holdout_size: number | null;
4513
+ /** Its training settings, each null while automatic. See {@link PipelineTrainingSettings}. */
4514
+ training: PipelineTrainingSettings;
4515
+ /** What automatic means for its model. See {@link PipelineTrainingDefaults}. */
4516
+ training_defaults: PipelineTrainingDefaults;
4428
4517
  minimums: PipelineMinimums;
4429
4518
  /**
4430
4519
  * The pinned base every version is trained over: from it, or, where the
@@ -4499,9 +4588,17 @@ export interface Pipeline {
4499
4588
  */
4500
4589
  month_spent_cents: number;
4501
4590
  active_run: PipelineActiveRun | null;
4502
- /** Winners only, v1 first. */
4591
+ /** How many versions it has made: every one is in `versions` from `getPipeline`, only the latest few and the live one on `listPipelines`. */
4592
+ versions_total: number;
4593
+ /** How many attempts it has made; `attempts` holds only the latest of them. */
4594
+ attempts_total: number;
4595
+ /** Winners only, v1 first: every one from `getPipeline`, the latest few and the live one on `listPipelines`. */
4503
4596
  versions: PipelineVersion[];
4504
- /** Every attempt, attempt 1 first. */
4597
+ /**
4598
+ * Its latest attempts, in attempt order: 20 from `getPipeline`, 5 on each
4599
+ * pipeline of `listPipelines`. `attempts_total` says how many there are,
4600
+ * and `listPipelineAttempts` pages through every one.
4601
+ */
4505
4602
  attempts: PipelineAttempt[];
4506
4603
  }
4507
4604
  export interface TrainingRuleDeleteResponse {
@@ -4658,12 +4755,23 @@ export interface BenchmarkHistoryResponse {
4658
4755
  points: BenchmarkHistoryPoint[];
4659
4756
  total: number;
4660
4757
  }
4758
+ /**
4759
+ * The pipeline list's `status` filter: `training` (an attempt is running),
4760
+ * `needs_you` (waiting on a member: a review or funds), `serving` (its live
4761
+ * version answers) and `paused`.
4762
+ */
4763
+ export type PipelineListStatus = 'training' | 'needs_you' | 'serving' | 'paused';
4661
4764
  export interface PipelineListParams {
4765
+ /** 100 when absent; at most 100. */
4766
+ limit?: number;
4662
4767
  /** How many of the newest pipelines to skip: the page after one that started at N starts at N + its length. */
4663
4768
  offset?: number;
4769
+ /** A case-insensitive substring of the name, the tag or the model. */
4770
+ q?: string;
4771
+ status?: PipelineListStatus;
4664
4772
  }
4665
4773
  export interface PipelineListResponse {
4666
- /** The newest pipelines first, at most the service's page (100). */
4774
+ /** The newest pipelines first, one page (100 unless `limit` says; at most 100). */
4667
4775
  pipelines: Pipeline[];
4668
4776
  /** Every pipeline in the workspace, including any not returned. */
4669
4777
  total: number;
@@ -4673,6 +4781,22 @@ export interface PipelineListResponse {
4673
4781
  export interface PipelineResponse {
4674
4782
  pipeline: Pipeline;
4675
4783
  }
4784
+ /** What became of an attempt, as `listPipelineAttempts` filters it. */
4785
+ export type PipelineAttemptResultFilter = 'became_version' | 'not_better' | 'inconclusive' | 'failed' | 'stopped' | 'running' | 'waiting_for_review';
4786
+ /** Which model's attempts: the pipeline's own (`main`) or its second model's (`second`). */
4787
+ export type PipelineAttemptModelFilter = 'main' | 'second';
4788
+ export interface PipelineAttemptListParams {
4789
+ /** 50 when absent; at most 200. */
4790
+ limit?: number;
4791
+ offset?: number;
4792
+ result?: PipelineAttemptResultFilter;
4793
+ model?: PipelineAttemptModelFilter;
4794
+ }
4795
+ /** One page of a pipeline's attempts, newest first. `total` follows the filters. */
4796
+ export interface PipelineAttemptListResponse {
4797
+ attempts: PipelineAttempt[];
4798
+ total: number;
4799
+ }
4676
4800
  /**
4677
4801
  * The body of `POST /api/loop/pipelines` and (every field optional)
4678
4802
  * `PUT /api/loop/pipelines/{id}`. Everything else -- the machines, the money,
@@ -4729,6 +4853,12 @@ export interface PipelineWriteRequest {
4729
4853
  /** 1 to 100, or null for no limit (the default). Counts versions, not attempts. */
4730
4854
  max_versions?: number | null;
4731
4855
  enabled?: boolean;
4856
+ /**
4857
+ * The pipeline's training settings. Absent: unchanged on an edit (automatic
4858
+ * on a create). null: every one back to automatic. An object: only the keys
4859
+ * it carries change, each null for automatic. See {@link PipelineTrainingRequest}.
4860
+ */
4861
+ training?: PipelineTrainingRequest | null;
4732
4862
  /** An edit's compare-and-set. */
4733
4863
  expected_revision?: number;
4734
4864
  }
@@ -4781,8 +4911,25 @@ export interface HeldBackFloors {
4781
4911
  max_percent: number;
4782
4912
  scored_rows: number;
4783
4913
  }
4914
+ /** Why a model is not offered for a pipeline ({@link LoopUnavailableModel.reason_code}). */
4915
+ export type LoopUnavailableReasonCode = 'not_trainable_sft_lora' | 'not_servable_as_adapter' | 'too_large_for_available_gpus' | 'not_verified' | 'registry_missing';
4916
+ /**
4917
+ * A model the platform knows that a pipeline cannot use, and why. `reason` is
4918
+ * one plain sentence to show as it is; `reason_code` is one of
4919
+ * {@link LoopUnavailableReasonCode}, typed as a string so a code added later
4920
+ * still carries its sentence.
4921
+ */
4922
+ export interface LoopUnavailableModel {
4923
+ id: string;
4924
+ name: string;
4925
+ family: string | null;
4926
+ reason_code: string;
4927
+ reason: string;
4928
+ }
4784
4929
  export interface LoopModelsResponse {
4785
4930
  models: LoopModel[];
4931
+ /** Every other model the platform knows, each with why it is not offered here. */
4932
+ unavailable: LoopUnavailableModel[];
4786
4933
  floors: PipelineFloors;
4787
4934
  }
4788
4935
  /** The promotion rule as the API reads it back. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runbios-sdk",
3
- "version": "0.2.22-dev.313",
3
+ "version": "0.2.22-dev.319",
4
4
  "description": "Official TypeScript SDK for the Run BiOS training and deployment platform API",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",