@collective-intelligence/external-apps-server-sdk 0.1.8 → 0.1.10

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
@@ -358,6 +358,34 @@ than waiting for a workflow in this bounded tool adapter. The
358
358
  shows distinct receipts and final results, independent resource authorization,
359
359
  and application-owned execution, recovery, failure, and duplicate protection.
360
360
 
361
+ ## Workflow results
362
+
363
+ Workflow invocation returns `runId`. A successful declared-output Workflow run
364
+ read through `runs.get` has `result: { nativeStatus: "completed", output }`;
365
+ the `completed` event from `runs.stream` carries that same object in `data`.
366
+ CI reads the validated, persisted Workflow output snapshot. The Gateway Tool's
367
+ response becomes public only through the Workflow's explicit `outputContract`
368
+ bindings and schema; selecting a Tool alone does not declare a Workflow result.
369
+ See the [binding example and lifecycle](../../../docs/architecture/external-app-gateway.md#workflow-results-and-observation).
370
+
371
+ The native worker finishes and persists output independently of the browser.
372
+ External terminal publication stays lazy on the next authorized run read, with
373
+ status, result and completion event committed together as an immutable receipt.
374
+ Existing terminal results and cancellation decisions survive repeated reads.
375
+ Owner-user/kind, Organization, Application and external audit-subject checks
376
+ still apply to both snapshots and events.
377
+
378
+ If reading fails, observe the original `runId` again; do not resubmit the
379
+ business command to retrieve its result. The browser SDK's `runs.follow`
380
+ returns an event page, propagates read failures, and does not retry them
381
+ automatically. Missing promised output is a technical invariant defect, not a
382
+ normal null-output or partial-success terminal result. There is no historical
383
+ terminal-run backfill or compatibility mode for this contract.
384
+
385
+ Keep analysis history and candidate results in Knowledge reads. Send a matching
386
+ completion notification only after the matching results are actually available
387
+ in Knowledge, even if the command run has already completed.
388
+
361
389
  ## Agent runs and conversations
362
390
 
363
391
  `agents.invoke({ agentId, input })` starts one Agent run and returns
@@ -517,6 +545,113 @@ const created = await ci.members.createOrGet({
517
545
  - Only `temporarily_unavailable` is retryable (the client retries it); a
518
546
  creation CI refuses is a non-retryable `internal_error`.
519
547
 
548
+ ## Durable files
549
+
550
+ A confidential application stores a file that a workflow run reads later, such
551
+ as the PDF of a signed form. The file is a private stored file of the
552
+ Organization the credentials were authorized for, with no expiry.
553
+
554
+ ```ts
555
+ const uploaded = await ci.files.upload({
556
+ contentBase64: pdf.toString("base64"),
557
+ contentType: "application/pdf",
558
+ fileName: "application.pdf",
559
+ organizationId,
560
+ sizeBytes: pdf.byteLength
561
+ })
562
+ // uploaded.value.result: { storageFileId }
563
+
564
+ await ci.workflows.invoke({
565
+ input: { document: { storageFileId: uploaded.value.result.storageFileId } },
566
+ organizationId,
567
+ workflowFqn
568
+ })
569
+ ```
570
+
571
+ - `contentType` is `application/pdf`, `image/jpeg` or `image/png`, and the file
572
+ holds 1 byte to 10 MiB. `sizeBytes` must match the decoded content. Other
573
+ files are refused with `invalid_request`.
574
+ - A workflow input can name the `storageFileId`; the nodes of a run of the same
575
+ Organization read the file through their stored file access. The file has no
576
+ public URL, and another Organization cannot read it.
577
+ - `attachments.upload` is different: its file belongs to one run and expires
578
+ after 24 hours.
579
+ - It needs the `ai:attachments` scope. Only service credentials may call it: a
580
+ delegated user token or an anonymous visitor gets `operation_not_allowed`.
581
+
582
+ ### Entity File fields
583
+
584
+ A File field of an entity schema takes a different flow: the file becomes a
585
+ File value of the entity, not a stored file a workflow reads.
586
+ `resources.createUpload` asks CI for a single-use upload slot, the server PUTs
587
+ the bytes straight to storage, and the entity write through CI's resources API
588
+ names the slot's `uploadId` as `{ upload }` for that File field key:
589
+
590
+ ```ts
591
+ const slot = await ci.resources.createUpload({
592
+ field: "memberLogo",
593
+ filename: "logo.png",
594
+ mimeType: "image/png",
595
+ organizationId,
596
+ schemaId: "apito.vetrina.schemas.member-company"
597
+ })
598
+ if (slot.ok) {
599
+ const { headers, method, uploadId, url } = slot.value.result
600
+ await fetch(url, { body: logo, headers, method })
601
+ // The entity write then sends { memberLogo: { upload: uploadId } }.
602
+ }
603
+ ```
604
+
605
+ - Two credentials can ask for a slot, as for entity writes: the member's
606
+ delegated token (`exchangeMemberSession`), writing as that member; or the
607
+ application's service credential, for schemas declared by the application's
608
+ own package only, writing as the Organization default service principal.
609
+ `schemaId` is the schema id or FQN.
610
+ - A refusal resolves `{ ok: false, error }` with CI's `correlationId` and a
611
+ `code`; `upload_not_supported` means the Organization storage cannot presign
612
+ uploads. Only `retryable` failures are retried.
613
+ - Each attempt, request and response body, is bounded by `timeoutMs`
614
+ (10 seconds by default); a CI that stops answering ends in a retryable
615
+ `unavailable`. `signal` cancels: a cancelled request rejects with the
616
+ signal's reason.
617
+ - The PUT is the caller's: send exactly `headers` to `url` with `method`. No
618
+ file bytes pass through CI's API. A slot is single use: the first successful
619
+ write consumes it; request a new one to retry.
620
+ - The write form of a File field key is `{ upload }` to add the uploaded file,
621
+ `{ id }` (or a File value read before) to keep a file, an ordered array for a
622
+ multiple field, or `null` to remove every file. Omit the key to keep its
623
+ files.
624
+ - `/runtime/effect` exports the Effect schemas `EntityFileValue` (a File value
625
+ as CI reads return it), `EntityFileWrite` (one file of a write) and
626
+ `EntityFileFieldWrite` (the write form of a File field key). Their types
627
+ equal the client SDK types of the same name. They validate CI entity
628
+ values; a portable resource form has no File field, so
629
+ `projectResourceSchema` refuses them.
630
+
631
+ ## Runtime origin
632
+
633
+ The browser origins of an Organization are the `allowedOrigins` that its
634
+ installed applications declare, plus one runtime origin per application. A
635
+ confidential application sets its runtime origin, for example the URL of each
636
+ new deployment; CI keeps storage CORS in line with the Organization origins, so
637
+ a browser on that origin can upload to a presigned storage URL.
638
+
639
+ ```ts
640
+ await ci.origins.setRuntime({
641
+ organizationId,
642
+ origin: `https://${process.env.VERCEL_URL}`
643
+ })
644
+ // value.result: { origin }
645
+ ```
646
+
647
+ - Each call replaces the application's previous runtime origin, so the list
648
+ does not grow; declare stable origins, such as the production site, in
649
+ `allowedOrigins`.
650
+ - `origin` is an exact HTTPS origin (`https://host[:port]`, no path), or a
651
+ loopback HTTP origin with a port; other values get `invalid_request`.
652
+ - It needs the `origins:write` scope. Only service credentials may call it: a
653
+ delegated user token or an anonymous visitor gets `operation_not_allowed`.
654
+
520
655
  ## Member company links
521
656
 
522
657
  An application links a member of its Organization to the Knowledge company
@@ -1407,6 +1542,21 @@ The adapter never retries or follows redirects. Cancellation or a lost response
1407
1542
  after dispatch remains an uncertain outcome; retain the original action for
1408
1543
  explicit resubmission.
1409
1544
 
1545
+ #### File uploads through the BFF
1546
+
1547
+ When the definition declares `capabilities.upload: true`, the handler also
1548
+ accepts `POST {basePath}/uploads` with `{ field, filename, mimeType }`. It calls
1549
+ `authorize` with the operation `upload`, then forwards the request with the
1550
+ verified grant to `/application/v1/resources/{schemaFqn}/uploads`. CI answers
1551
+ the File upload `{ uploadId, url, method: "PUT", headers }`; the browser PUTs
1552
+ the bytes straight to that storage URL, so no file bytes pass through the BFF.
1553
+ The entity write that names `{ upload: uploadId }` follows the normal update
1554
+ path. `upload_not_supported` (the Organization storage can not presign) maps to
1555
+ HTTP 400. Without the capability the request answers `capability_not_supported`
1556
+ and is not forwarded. A local `defineEntityResource` binding answers
1557
+ `capability_not_supported`, and a `defineCrudResource` handler answers 404 for
1558
+ this path. The client SDK README documents the write form.
1559
+
1410
1560
  ## Optional local user authentication
1411
1561
 
1412
1562
  Import `createBetterAuth` only from