Repository navigation
v0.34.0
@pipelex/sdk on npm
Highlights
A method runs from the shell, with no project around it. npx @pipelex/sdk run runs a method on the hosted API through the SDK's durable run lifecycle and prints its main output as JSON, and npx @pipelex/sdk script writes a shell script that runs it with this version pinned. pipelex-sdk on PyPI ships the same command, and both are held to one recorded table of cases.
Added
isGatewayCutOff: the rule the blockingexecuteapplies to tell the hosted gateway's ~30-second cut-off from a refusal is now exported, asisGatewayCutOff(error, elapsedMs): a503or504answer, or the client's own time limit (ABORT_TIMEOUT), at least ~28 seconds after the request was sent. A caller timing its own start, fromstartAndWaitForResult'sonStartingfor one, reads it to learn that a start the gateway gave up on may still create the run, as thepipelex-sdkcommand does.docs/errors.mddescribes it.signalonprepareInputsanduploadFile: the request ofprepareInputstakes an optionalsignal, which stops the preparation between its steps, a file's read and its upload included: once it has aborted, neither the pipe I/O request nor any upload starts, andprepareInputsthrows the abort.uploadFile's options take the samesignal, read once the file's bytes are in hand, right before the upload request. A request already sent runs to its end.docs/input-preparation.mddescribes it.filenameon every upload failure:UploadAuthenticationError,UnsupportedUploadCapabilityErrorandUploadTransportErrornow carryfilename, the fileuploadFilewas sending, asRejectedAssetErroralready did, so a consumer preparing several files says which one failed without parsing the message.uploadWithGrantleaves it undefined.InvalidInputValueErrorandMethodLoadError:prepareInputsnow tells the inputs' own mistakes from a method that does not load by class. Adata:URL at a file input that does not decode, and a value of a type no file input takes, throwInvalidInputValueError, where they threw a plainInputPreparationError; a pipe I/O answer saying the method does not load (is_valid: false) throwsMethodLoadError, carrying the answer'svalidationErrorsand its own message asserverMessage, where only the first item's message reached the caller, inside the error's sentence. Both derive fromInputPreparationError, keep its verdict (input, not retryable) and keep their messages, so acatchon the base is unchanged.docs/input-preparation.mdlists each preparation failure.- The
pipelex-sdkcommand: the package now declares abin, sonpx @pipelex/sdk run --method <address | mt_id | bundle> --inputs inputs.jsonruns a method on the hosted API through the SDK's durable run lifecycle and prints its main output as JSON on stdout, the run id, each uploaded file and every error going to stderr;--inputs-templateprints the object--inputstakes; Ctrl-C leaves the run going and names it, and a failure that leaves a run going, or possibly created, says so.npx @pipelex/sdk script --method <address | mt_id>checks the method and writes a shell script that runs it with this SDK's version pinned. The key comes fromPIPELEX_API_KEYonly and no.envfile is read. The command adds no runtime dependency, and the package entry does not export it.docs/cli.mddescribes it, andtests/fixtures/cli-cases.jsonrecords its behaviour case by case, which the Python SDK's command of the same name runs too. onStartedandonStartingonstartAndWaitForResult: the second argument is nowStartAndWaitForResultOptions, the wait's options plusonStarted, called once with the start acknowledgement as soon as the durable run exists and before the first poll, so a caller that waits throughstartAndWaitForResultholds the run's id while it waits, to show it, log it or resume the run by it after an interrupt; it is never called on the blocking path, which has no run id to give.onStartingis called right before each request that may create a run is sent, the start or the blocking execute, so a caller that stops waiting before then knows that no run was started; it is never called once thesignalhas aborted.docs/run-results.mddescribes them.
Fixed
- The blocking
executetimes its request on the monotonic clock: it measured the time to the hosted gateway's ~30-second cut-off withDate.now, so a change of the system's time while the request was out, such as a time sync stepping the clock, could turn a fast503into aPipelineExecuteTimeoutErroror hide a real cut-off. It now usesperformance.now, which no such change moves. startAndWaitForResultsends nothing once its signal has aborted: an abort that landed while theGET /v1/versionhandshake was in flight stopped nothing, so the start or the blocking execute was sent once the handshake answered, and a run was created for a caller that had already given up. The signal is now read after the handshake and before the fallback to the blocking execute, and the call throws the abort without creating a run.docs/run-results.mddescribes it.- A success answer that is not UTF-8 is an answer the SDK cannot read: every route read a
2xxbody asResponse.text()does, a byte that is not UTF-8 turning into U+FFFD, so a JSON answer holding such a byte could hand back a mangled value, a run id included. It now throws theApiResponseErrorof an answer the SDK cannot read, as the Python SDK refuses one; a refusal's body is still read leniently for its reason. - A start carrying an inline bundle gets the blocking ceiling:
start()gave a request whose bundle was inline asmthds_contentsthe poll's 30-second limit, so a large bundle on a slow uplink timed out onPOST /v1/startwhile the same payload succeeded on the blocking execute; it now gets the blocking ceiling, as a bundle infilesorbundle_b64and amethod_refalready did. - An unanswered version handshake throws at once: a
GET /v1/versionthat got no answer was taken for a hosted API for the client's lifetime, andstartAndWaitForResultsent the start anyway; it now throws itsApiUnreachableErrorat once, sends no start and caches nothing, so the next call asks again. An answer that is no usable version, a body that arrived and does not decode, such as a broken gzip stream, included, is still taken for a hosted API, as the Python SDK takes it. - A refused base URL is no longer echoed whole: the
RequestArgumentErrorthe constructor throws for a base URL that is not host-only quoted the value verbatim, so the credentials, the path or the query token the rule exists to keep out reached logs and any page that shows an error's message. The refusal now shows the scheme and the host alone and names the parts beyond them without their text (Invalid API base URL "https://api.example.com" with credentials and a query (not shown): …), and a value that is not an http or https URL is not shown at all.
pipelex-sdk on PyPI
Highlights
A method runs from the shell, with no project around it. uvx pipelex-sdk run runs a method on the hosted API through the SDK's durable run lifecycle and prints its main output as JSON, and uvx pipelex-sdk script writes a shell script that runs it with this version pinned. @pipelex/sdk on npm ships the same command, and both are held to one recorded table of cases. Every route now reports a missing answer the same way: a request that gets no answer raises ApiUnreachableError, whose code tells a read or write timeout from a failure to connect.
Added
is_gateway_cut_off: the rule the blockingexecuteapplies to tell the hosted gateway's ~30-second cut-off from a refusal is now public, aspipelex_sdk.client.is_gateway_cut_off(exc, elapsed_seconds): a503or504answer, or the client's own read or write timeout (ABORT_TIMEOUT), at least ~28 seconds after the request was sent. A caller timing its own start, fromstart_and_wait'son_startingfor one, reads it to learn that a start the gateway gave up on may still create the run, as thepipelex-sdkcommand does.docs/architecture.mddescribes it.- The
pipelex-sdkcommand: the package now declares a console script, souvx pipelex-sdk run --method <address | mt_id | bundle> --inputs inputs.jsonruns a method on the hosted API through the SDK's durable run lifecycle and prints its main output as JSON on stdout, the run id, each uploaded file and every error going to stderr;--inputs-templateprints the object--inputstakes; Ctrl-C leaves the run going and names it, and a failure that leaves a run going, or possibly created, says so.uvx pipelex-sdk script --method <address | mt_id>checks the method and writes a shell script that runs it with this SDK's version pinned (exec uvx pipelex-sdk@X.Y.Z run …). The key comes fromPIPELEX_API_KEYonly and no.envfile is read. The command adds no dependency, and nothing in the library imports it. It is the twin of@pipelex/sdk's command of the same name, which it shares no code with: both run every case of one recorded table,tests/fixtures/cli-cases.json, so they take the same flags and print the same lines.docs/cli.mddescribes it, andmake test-packageruns the built wheel's command throughuvxin CI. InvalidInputValueErrorandMethodLoadError:prepare_inputsnow tells the inputs' own mistakes from a method that does not load by class. Adata:URL at a file input that does not decode, and a value of a type no file input takes, raiseInvalidInputValueError, where they raised a plainInputPreparationError; a pipe I/O answer saying the method does not load (is_valid: false) raisesMethodLoadError, carrying the answer'svalidation_errorsand its own message asserver_message, where only the first item's message reached the caller, inside the error's sentence. Both derive fromInputPreparationErrorand keep their messages, so anexcepton the base is unchanged. They are the twins of@pipelex/sdk's classes of the same names;docs/input-preparation.mdlists each preparation failure.filenameandstatuson upload failures:UnsupportedUploadCapabilityError,UploadAuthenticationErrorandUploadTransportErrornow carryfilename, the fileupload_filewas sending, asRejectedAssetErroralready did, andUploadTransportErrorcarries the answer'sstatuswhen one came back, so a caller preparing several files says which one failed without parsing the message. They are the twins of@pipelex/sdk's fields;docs/input-preparation.mdlists them.on_startedandon_startingonstart_and_wait:on_startedtakes a callable that is called once with the start acknowledgement, aPipelexRunResultStart, as soon as the durable run exists and before the first poll, so a caller that waits throughstart_and_waitholds the run's id while it waits, to show it, log it or resume the run by it after cancelling the wait; it is never called on the blocking path, which has no run id to give.on_startingtakes a callable with no argument, called right before each request that may create a run is sent, the start or the blocking execute, so a caller that stops waiting before then knows that no run was started. An exception either raises propagates before the next request. They are the twins of@pipelex/sdk'sonStartedandonStarting;docs/run-results.mddescribes them.
Changed
- A connection or pool timeout keeps its own code (Breaking):
ApiUnreachableError.codeisABORT_TIMEOUTonly for a read or write timeout, a request that reached the API and outlasted its time limit; a timeout while connecting, or while waiting for a pooled connection, now carriesConnectTimeoutorPoolTimeout, where every timeout carriedABORT_TIMEOUT, and a blockingexecutethat could not connect is no longer taken for one the gateway cut off. A caller that branched onABORT_TIMEOUTfor every timeout branches on the httpx names too. pipe_iosends only what a request sets: the body ofPOST /v1/pipe-ionow leaves out every field at its default as well as everyNone, so the route gets what@pipelex/sdksends for the same call and applies the same defaults.
Fixed
execute,start,validate,modelsandversionraiseApiUnreachableErrorwhen no answer comes back (Breaking): a refused connection, a DNS failure, a TLS failure or a timeout on the protocol routes the client inherits frommthdsescaped as httpx's own exception, outsidePipelineRequestError, while the run reads and the product routes already raisedApiUnreachableError. Every route now raises it,start_and_waitincluded, so a caller that caughthttpx.TransportErrororhttpx.TimeoutExceptionfrom these routes catchesApiUnreachableErrorand reads itscode(ABORT_TIMEOUTfor a read or write timeout, the httpx failure's name otherwise). A blockingexecutecut off by a timeout after about 28 seconds still raisesPipelineExecuteTimeoutError, whose cause is now thatApiUnreachableError. A body httpx cannot decode, such as a broken gzip stream, raises it too, with the codeDecodingError, where httpx'sDecodingErrorescaped, as@pipelex/sdkreports one (Z_DATA_ERROR).- A refused base URL is no longer echoed whole: the
PipelineRequestErrorthe constructor raises for a base URL that is not host-only quoted the value verbatim, so the credentials, the path, its;parameters or the query token the rule exists to keep out reached logs and any page that shows an error's message. The refusal now shows the scheme and the host alone and names the parts beyond them without their text (Invalid API base URL "https://api.example.com" with credentials and a query (not shown): it must be host-only …), and a value that is not an http or https URL is not shown at all, word for word as@pipelex/sdkrefuses it. InvalidLocalSourceErrornames the system's error code: a local file that input preparation cannot read is now reported asLocal file cannot be read: "<path>" (ENOENT)., naming the code (ENOENT,EACCES, …) where it named the Python exception class, as@pipelex/sdkreports it.- The version handshake and a plain start no longer wait as long as a blocking run:
version(), the handshake ofstart_and_wait, and astartsending neither a bundle nor amethod_refwaitedrequest_timeout_secondsfor an answer, the blocking execute's ceiling by default; they now haverequest_timeout_secondscapped at the poll's per-request limit, the hosted gateway's own cut-off, so a caller who set a shorter limit to fail fast keeps it, while a start carrying a bundle, inline asmthds_contentsor asfilesorbundle_b64, or amethod_ref, which the server resolves before it answers, keepsrequest_timeout_seconds. A handshake that gets no answer now raises itsApiUnreachableErrorat once, sends no start and caches nothing, so the next call asks again, where it was taken for a hosted API for the client's lifetime; one answered with a body that is not JSON or not UTF-8, such as a gateway's HTML page, which escaped asjson.JSONDecodeErrororUnicodeDecodeErrorand sent no start, is now taken for a hosted API, as an error status, a body that is no version and a body that does not decode already were, and as@pipelex/sdktakes them. - A cancelled
start_and_waitorupload_filesends nothing more: a task cancelled while the version handshake's answer was read could go on to send the start or the blocking execute, and one cancelled whileupload_fileread and encoded a file could go on to upload it; each now raisesasyncio.CancelledErrorbefore that request, as@pipelex/sdkstops on an abortedsignal. typing-extensions4.4.0 or later: the package importstyping_extensions.override, which first shipped in 4.4.0, yet declaredtyping-extensions>=4.0.0, so an environment holding an older release failed at import. It now requires 4.4.0 or later.wait_for_resultstops when its task is cancelled, on Python 3.11 too: on Python 3.11, cancelling the task awaitingwait_for_resultorstart_and_waitin the loop step where a poll answered was swallowed, and the wait polled on until the run ended ortimeout_secondsran out. It now raisesasyncio.CancelledErroron every supported Python version, so anasyncio.timeoutaround the wait, a cancelled request handler or Ctrl-C underasyncio.runstops it.
@pipelex/create-method-app on npm, with the method-app templates
Changed
- The web app template runs on
@pipelex/sdk0.33.0, withconfig_invalidin place ofconfig_missing(Breaking): bumped from 0.30.0. Theconfig_missingkind classifiedClientAuthenticationError, which the SDK never threw and no longer exports;config_invalidnow classifies aPIPELEX_BASE_URLnaming more than a host, which the SDK refuses before sending any request and the app used to report as "Something went wrong". A2xxwhose body the SDK cannot read is shown as "Pipelex API sent an answer the app could not read", with the raw body in the technical details, where it used to be an unexplained error. - The web app template's rejected request or server error always says whether a re-run can help: its retry line is the SDK's verdict, the API's own
retryablewhen its problem document sent one and otherwise the SDK's reading of the HTTP status, so a 4xx says a re-run unchanged fails the same way and a passing 5xx offers one; a server error whose type the app recognizes (missing provider credentials, no inference backend, a method definition the server rejected) stays final unless the API says otherwise. The technical details mark a member of the verdict the API did not send as the SDK's reading, and name the recognized error type whose own verdict overrides it. - The web app template's failed upload offers a retry where one can help: storage answering that it does not implement the upload (its
501) now reads as final instead of "usually temporary", while storage timing out or throttling the upload, and two writes colliding, are offered a retry, all following the SDK's verdict.
Fixed
- The web app template's durable poll rides out a rate limit: a status read the API refuses with a
429or a408is read again, as a gateway5xxalready was, where it used to abandon a run that was still executing; a read that cannot pass, such as a501, gives up at once. When the poll does give up, the error no longer invites a re-run, since the run may still be executing and running it again would start a second one.
Security
- The web app template no longer shows a refused
PIPELEX_BASE_URLto its visitors: the SDK's refusal of a value naming more than a host quotes that value whole, and the app showed it in the technical details of the error every visitor sees, credentials and query included. The details now show only its scheme and host, and name the parts beyond them without their content.
The JavaScript starter, Pipelex/pipelex-starter-js
Changed
@pipelex/sdk0.33.0, andconfig_invalidin place ofconfig_missing(Breaking): bumped from 0.30.0. Theconfig_missingkind classifiedClientAuthenticationError, which the SDK never threw and no longer exports;config_invalidnow classifies aPIPELEX_BASE_URLnaming more than a host, which the SDK refuses before sending any request and the starter used to report as "Something went wrong". A2xxwhose body the SDK cannot read is shown as "Pipelex API sent an answer the app could not read", with the raw body in the technical details, where it used to be an unexplained error.- A rejected request or a server error always says whether a re-run can help: its retry line is the SDK's verdict, the API's own
retryablewhen its problem document sent one and otherwise the SDK's reading of the HTTP status, so a 4xx says a re-run unchanged fails the same way and a passing 5xx offers one; a server error whose type the starter recognizes (missing provider credentials, no inference backend, a method definition the server rejected) stays final unless the API says otherwise. The technical details mark a member of the verdict the API did not send as the SDK's reading, and name the recognized error type whose own verdict overrides it. - A failed upload offers a retry only where one can help: storage answering that it does not implement the upload (its
501) now reads as final instead of "usually temporary", and storage timing out or throttling the upload is offered a retry, both following the SDK's verdict.
Fixed
- A durable run's poll rides out a rate limit: a status read the API refuses with a
429or a408is read again, as a gateway5xxalready was, where it used to abandon a run that was still executing; a read that cannot pass, such as a501, gives up at once. When the poll does give up, the error no longer invites a re-run, since the run may still be executing and running it again would start a second one.
Security
- A refused
PIPELEX_BASE_URLno longer reaches the browser: the SDK's refusal of a value naming more than a host quotes that value whole, and the starter showed it in the technical details of the error every visitor sees, credentials and query included. The details now show only its scheme and host, and name the parts beyond them without their content.
The Python starter, Pipelex/pipelex-starter-python
Changed
pipelex-sdk0.31.0 andmthds0.19.0 are the floors (Breaking): bumped from 0.30.0 and 0.18.0, themthdsthat bothpipelex-sdkandpipelexnow pin, so a project can install the two together again.mthds0.19.0 reads a stuff'sconceptas its ref string, which nothing in the template reads; a project's own code that readstuff.concept.codereadsstuff.conceptinstead.