@sprigr/apps-app-sdk 0.5.0 → 0.7.0
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 +26 -1
- package/dist/index.d.ts +248 -15
- package/dist/index.js +65 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,31 @@ The runtime injects the `env.SPRIGR` host object **only on `/__sprigr/*` dispatc
|
|
|
17
17
|
- `resolveInstallBridge(env)` / `installTokenPost(bridge, path, body, opts?)` — build your own `/internal/wfp/*` fallback (collections, files, inbox) with the auth and error extraction handled.
|
|
18
18
|
- `overlaySprigr(env, sprigr)` — overlay a patched `SPRIGR` via `Object.create`. Never rebuild a dispatch-path env by spread: the real bindings live on the prototype and `SPRIGR` is non-enumerable, so `{ ...env }` yields an env whose `DB` is `undefined`.
|
|
19
19
|
|
|
20
|
+
## Per-actor tool wrappers
|
|
21
|
+
|
|
22
|
+
An agent-facing tool must resolve the **calling** identity's own credential, or refuse. It must never fall back to another connected account, not even for a read: an install is company-wide, but access is not.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// src/handlers/wrap.ts: bind the options once
|
|
26
|
+
import { createToolWrappers, NotConnectedError } from '@sprigr/apps-app-sdk';
|
|
27
|
+
|
|
28
|
+
export const { tool, actorTool } = createToolWrappers<MyEnv>({
|
|
29
|
+
notConnectedHint: 'Call my_connect and send the user the link.',
|
|
30
|
+
mapError: (err) =>
|
|
31
|
+
err instanceof MyApiError ? { error: err.message, status: err.status } : null,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// src/handlers/things.ts
|
|
35
|
+
export default { list_things: actorTool(async (env, actor, args) => { /* ... */ }) };
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- `actorTool(fn)`: reads the platform-stamped `args.actor`, returns `412 no_caller_identity` when it is absent, and maps a thrown `NotConnectedError` to `412 not_connected` with your connect hint. Identity comes only from the nested `args.actor`; flat body fields are agent-supplied and spoofable.
|
|
39
|
+
- `tool(fn)`: for handlers with genuinely no caller (schedules, webhooks, platform mirrors). Those need a *designated service connection*, which is not licence for an agent-facing handler to borrow one.
|
|
40
|
+
- `NotConnectedError`: throw from your token resolver when the **caller** has no connection. Don't throw it for "the install has none", which is a different fault with a different fix.
|
|
41
|
+
- `isNotConnectedError(err)`: matches by class *and* by `name`, so it still works when the vendor pattern puts two copies of this module in one isolate and `instanceof` would silently fail.
|
|
42
|
+
|
|
43
|
+
**Why this is in the SDK:** every app hand-rolled this, and one shipped a version that fell back to "the first connected actor on the install" when the caller had none. One person's consent then exposed their account to every agent on that install, in production.
|
|
44
|
+
|
|
20
45
|
## Webhook callback URLs
|
|
21
46
|
|
|
22
47
|
- `resolvePlatformWebhookBase(env)` / `buildMarketplaceWebhookUrl(env, installId, topicPath)` — env-correct platform host, so a staging install never registers prod-pointing subscriptions.
|
|
@@ -26,7 +51,7 @@ The runtime injects the `env.SPRIGR` host object **only on `/__sprigr/*` dispatc
|
|
|
26
51
|
- `fetchWithRetry` — rate-limit-header-aware, jittered retry
|
|
27
52
|
- `constantTimeEqual(a, b)` — bearer-secret verification
|
|
28
53
|
- `encodeState` / `decodeState` — OAuth state base64url
|
|
29
|
-
- `parseActor` / `actorKey` — per-actor token scoping
|
|
54
|
+
- `parseActor` / `actorKey` / `ownerFromActorKey` — per-actor token scoping
|
|
30
55
|
- `putAppFile` / `putAppFileStream` / `appFileUrl` / `getAppFile` / `listAppFiles` / `deleteAppFile` — durable app-scoped file storage from outside the injected bridge
|
|
31
56
|
- `fetchFileBytes` / `fetchFileAsBase64` / `bytesToBase64` / `base64ToBytes` — file byte helpers
|
|
32
57
|
|
package/dist/index.d.ts
CHANGED
|
@@ -582,6 +582,138 @@ interface InboxOwner {
|
|
|
582
582
|
*/
|
|
583
583
|
declare function ownerFromActorKey(key: string | null | undefined): InboxOwner | null;
|
|
584
584
|
|
|
585
|
+
/**
|
|
586
|
+
* Adapters between the marketplace wrapper's calling convention and an app's
|
|
587
|
+
* inner handler signatures, with fail-closed per-actor authorization built in.
|
|
588
|
+
*
|
|
589
|
+
* The wrapper calls `handler(args, env, ctx)`, args first. Inner handlers are
|
|
590
|
+
* usually written `(env, args)` or `(env, actor, args)`, so something has to
|
|
591
|
+
* flip them and shape thrown errors into a result an agent can read.
|
|
592
|
+
*
|
|
593
|
+
* tool(fn) actor-less handlers: schedules, webhooks, internal work.
|
|
594
|
+
* actorTool(fn) per-actor handlers: reads the platform-stamped
|
|
595
|
+
* `args.actor`, fails closed with 412 when it is absent, and
|
|
596
|
+
* maps NotConnectedError to a 412 `not_connected`.
|
|
597
|
+
*
|
|
598
|
+
* WHY THIS IS IN THE SDK. Every app hand-rolled this wrapper, and the gorgias
|
|
599
|
+
* app got it wrong in the way that matters: when the caller had no connection
|
|
600
|
+
* it fell through to "the first connected actor on the install", so one
|
|
601
|
+
* person's consent made their whole helpdesk readable and writable by every
|
|
602
|
+
* agent on a company-wide install. That reached production. The rule is not
|
|
603
|
+
* hard, but it is easy to write a plausible-looking fallback for, so the
|
|
604
|
+
* correct version belongs in one place that apps import rather than copy.
|
|
605
|
+
*
|
|
606
|
+
* THE RULE: an agent-facing tool resolves the CALLING identity's own
|
|
607
|
+
* credential, or refuses. Never another actor's, not even to be helpful, and
|
|
608
|
+
* not even for a read. Background paths (schedules, webhooks, platform
|
|
609
|
+
* mirrors) genuinely have no caller and need a designated service connection;
|
|
610
|
+
* that is what `tool()` is for, and it is not licence for an agent-facing
|
|
611
|
+
* handler to borrow one.
|
|
612
|
+
*/
|
|
613
|
+
|
|
614
|
+
/** What every wrapped handler returns. `ok:false` never throws past the wrapper. */
|
|
615
|
+
type ToolResult<R> = {
|
|
616
|
+
ok: true;
|
|
617
|
+
result: R;
|
|
618
|
+
} | {
|
|
619
|
+
ok: false;
|
|
620
|
+
error: string;
|
|
621
|
+
hint?: string;
|
|
622
|
+
status?: number;
|
|
623
|
+
};
|
|
624
|
+
/**
|
|
625
|
+
* Throw this from a token resolver when the CALLING actor has no connection
|
|
626
|
+
* of their own. `actorTool` turns it into a 412 `not_connected` carrying the
|
|
627
|
+
* app's connect hint, which is the agent's cue to mint an authorize link
|
|
628
|
+
* rather than retry.
|
|
629
|
+
*
|
|
630
|
+
* Do not throw it for "the install has no connection at all" on a background
|
|
631
|
+
* path: that is a different failure with a different fix (an admin
|
|
632
|
+
* designating a service connection), and collapsing the two sends the agent
|
|
633
|
+
* chasing a consent link it cannot complete.
|
|
634
|
+
*/
|
|
635
|
+
declare class NotConnectedError extends Error {
|
|
636
|
+
constructor(message?: string);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* True for a NotConnectedError, INCLUDING one thrown by a different copy of
|
|
640
|
+
* this module.
|
|
641
|
+
*
|
|
642
|
+
* `instanceof` alone is wrong here and would fail silently. Apps reach this
|
|
643
|
+
* package two ways: a normal npm dependency, and the legacy `sprigrVendor`
|
|
644
|
+
* mirror that copies the source into `src/lib/vendor/`. An app that vendors
|
|
645
|
+
* the SDK while a dependency resolves its own npm copy has TWO distinct
|
|
646
|
+
* `NotConnectedError` classes at runtime, and `instanceof` across them is
|
|
647
|
+
* false. The failure mode is the bad one: the error falls through to the
|
|
648
|
+
* generic branch, the agent gets an opaque message instead of `not_connected`,
|
|
649
|
+
* and never learns to offer a connect link. Matching on `name` as well costs
|
|
650
|
+
* nothing and survives the boundary.
|
|
651
|
+
*/
|
|
652
|
+
declare function isNotConnectedError(err: unknown): boolean;
|
|
653
|
+
interface ToolWrapperOptions {
|
|
654
|
+
/**
|
|
655
|
+
* Hint returned alongside `not_connected`. Name the app's connect tool and
|
|
656
|
+
* frame it as a one-click authorization rather than an error, because this
|
|
657
|
+
* string is what the agent repeats to the user.
|
|
658
|
+
*/
|
|
659
|
+
notConnectedHint?: string;
|
|
660
|
+
/**
|
|
661
|
+
* Hint returned alongside `no_caller_identity`.
|
|
662
|
+
*/
|
|
663
|
+
noCallerIdentityHint?: string;
|
|
664
|
+
/**
|
|
665
|
+
* Shape an app-specific error (a provider API error carrying an HTTP
|
|
666
|
+
* status) into a result. Return null to fall through to the generic
|
|
667
|
+
* `{ error: message }`. Runs AFTER the not_connected check.
|
|
668
|
+
*
|
|
669
|
+
* mapError: (err) => err instanceof GraphApiError
|
|
670
|
+
* ? { error: err.message, status: err.status }
|
|
671
|
+
* : null,
|
|
672
|
+
*/
|
|
673
|
+
mapError?: (err: unknown) => {
|
|
674
|
+
error: string;
|
|
675
|
+
status?: number;
|
|
676
|
+
hint?: string;
|
|
677
|
+
} | null;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Wrap an actor-less handler. Catches throws into `{ ok: false, error }` so an
|
|
681
|
+
* agent caller never sees a bare exception.
|
|
682
|
+
*/
|
|
683
|
+
declare function tool<Env, A, R>(fn: (env: Env, args: A) => Promise<R>, opts?: ToolWrapperOptions): (args: A, env: Env) => Promise<ToolResult<R>>;
|
|
684
|
+
/**
|
|
685
|
+
* Wrap a per-actor handler. Fails closed twice over: no stamped identity is
|
|
686
|
+
* `no_caller_identity`, and a caller with no connection of their own is
|
|
687
|
+
* `not_connected`. Both 412, both distinct on purpose: the first is a
|
|
688
|
+
* plumbing fault the agent should retry differently, the second the agent can
|
|
689
|
+
* resolve itself by sending the user a link.
|
|
690
|
+
*
|
|
691
|
+
* Identity comes ONLY from the nested, platform-stamped `args.actor`.
|
|
692
|
+
* `parseActor` deliberately ignores flat body fields, which are agent-supplied
|
|
693
|
+
* and spoofable; honouring one would let a caller name any colleague and act
|
|
694
|
+
* as them.
|
|
695
|
+
*/
|
|
696
|
+
declare function actorTool<Env, A, R>(fn: (env: Env, actor: Actor, args: A) => Promise<R>, opts?: ToolWrapperOptions): (args: A, env: Env) => Promise<ToolResult<R>>;
|
|
697
|
+
/**
|
|
698
|
+
* Bind the options once and get both wrappers typed to the app's env, so
|
|
699
|
+
* handler files call `actorTool(fn)` with no per-call ceremony:
|
|
700
|
+
*
|
|
701
|
+
* // src/handlers/wrap.ts
|
|
702
|
+
* export const { tool, actorTool } = createToolWrappers<Ms365Env>({
|
|
703
|
+
* notConnectedHint: 'Call ms_connect and send the user the link…',
|
|
704
|
+
* mapError: (err) =>
|
|
705
|
+
* err instanceof GraphApiError ? { error: err.message, status: err.status } : null,
|
|
706
|
+
* });
|
|
707
|
+
*
|
|
708
|
+
* Options bound once is the point: an app that repeats them at each of fifty
|
|
709
|
+
* call sites will drift, and the one that drifts is the hint telling a user
|
|
710
|
+
* how to connect.
|
|
711
|
+
*/
|
|
712
|
+
declare function createToolWrappers<Env>(opts?: ToolWrapperOptions): {
|
|
713
|
+
tool: <A, R>(fn: (env: Env, args: A) => Promise<R>) => (args: A, env: Env) => Promise<ToolResult<R>>;
|
|
714
|
+
actorTool: <A, R>(fn: (env: Env, actor: Actor, args: A) => Promise<R>) => (args: A, env: Env) => Promise<ToolResult<R>>;
|
|
715
|
+
};
|
|
716
|
+
|
|
585
717
|
/**
|
|
586
718
|
* App-scoped R2 file storage (platform feature, 2026-06).
|
|
587
719
|
*
|
|
@@ -769,6 +901,56 @@ interface SprigrDataSearchOpts {
|
|
|
769
901
|
page?: number;
|
|
770
902
|
/** Results per page. Default 20, platform-clamped to 100. */
|
|
771
903
|
hitsPerPage?: number;
|
|
904
|
+
/**
|
|
905
|
+
* LOGICAL index name — a key of the manifest's `data_indexes` map.
|
|
906
|
+
* Required for apps that declare `data_indexes` (the platform rejects a
|
|
907
|
+
* call without it, naming the declared indexes); must be OMITTED for
|
|
908
|
+
* legacy single-`data_index` apps. An undeclared name is rejected with
|
|
909
|
+
* `unknown_data_index`, never auto-created.
|
|
910
|
+
*/
|
|
911
|
+
index?: string;
|
|
912
|
+
/**
|
|
913
|
+
* Explicit shard subset for a sharded logical index: `['2026']` for
|
|
914
|
+
* `shard_by: 'year'`, `['2026-05', '2026-06']` for `'month'`. Omit to
|
|
915
|
+
* search every existing shard (newest first). Narrowing here is also the
|
|
916
|
+
* escape hatch when merged pagination hits the per-shard page cap.
|
|
917
|
+
*/
|
|
918
|
+
shards?: string[];
|
|
919
|
+
/**
|
|
920
|
+
* Server-side aggregations computed over ALL matching docs (not just the
|
|
921
|
+
* returned page). On a sharded logical index the platform merges shard
|
|
922
|
+
* results exactly (sum/count add, min/max take extremes, avg re-derives
|
|
923
|
+
* count-weighted).
|
|
924
|
+
*/
|
|
925
|
+
aggregations?: SprigrDataAggregationSpec[];
|
|
926
|
+
}
|
|
927
|
+
/** One server-side aggregation. Wire fields are snake_case (platform decision 0013). */
|
|
928
|
+
interface SprigrDataAggregationSpec {
|
|
929
|
+
/** Response key for this aggregation's result block. */
|
|
930
|
+
name: string;
|
|
931
|
+
/** Fold operation. `count` ignores `field`. */
|
|
932
|
+
op: 'sum' | 'avg' | 'min' | 'max' | 'count';
|
|
933
|
+
/** Numeric field to fold. Required for sum/avg/min/max. */
|
|
934
|
+
field?: string;
|
|
935
|
+
/** Bucket by this field; omit for one whole-result-set value. */
|
|
936
|
+
group_by?: string;
|
|
937
|
+
}
|
|
938
|
+
/** One aggregation result cell: the op value plus (for non-count ops) the numeric-value count. */
|
|
939
|
+
interface SprigrDataAggregationCell {
|
|
940
|
+
sum?: number | null;
|
|
941
|
+
avg?: number | null;
|
|
942
|
+
min?: number | null;
|
|
943
|
+
max?: number | null;
|
|
944
|
+
count?: number;
|
|
945
|
+
}
|
|
946
|
+
interface SprigrDataAggregationResult {
|
|
947
|
+
op: 'sum' | 'avg' | 'min' | 'max' | 'count';
|
|
948
|
+
field: string | null;
|
|
949
|
+
group_by: string | null;
|
|
950
|
+
/** Present for ungrouped specs. */
|
|
951
|
+
value?: SprigrDataAggregationCell;
|
|
952
|
+
/** Present for grouped specs: bucket value -> cell. */
|
|
953
|
+
groups?: Record<string, SprigrDataAggregationCell>;
|
|
772
954
|
}
|
|
773
955
|
interface SprigrDataSearchResult {
|
|
774
956
|
ok: boolean;
|
|
@@ -779,14 +961,34 @@ interface SprigrDataSearchResult {
|
|
|
779
961
|
nbHits: number;
|
|
780
962
|
page: number;
|
|
781
963
|
facetCounts?: Record<string, Record<string, number>>;
|
|
782
|
-
/**
|
|
964
|
+
/** Present when the request carried `aggregations`. */
|
|
965
|
+
aggregations?: Record<string, SprigrDataAggregationResult>;
|
|
966
|
+
/**
|
|
967
|
+
* The logical index name for multi-index apps, else the physical index
|
|
968
|
+
* that was queried (informational).
|
|
969
|
+
*/
|
|
783
970
|
index: string;
|
|
971
|
+
/** Multi-index apps: the physical index/shard names this read covered. */
|
|
972
|
+
physical_indexes?: string[];
|
|
973
|
+
}
|
|
974
|
+
/** Options shared by data methods that can target a logical index. */
|
|
975
|
+
interface SprigrDataIndexOpts {
|
|
976
|
+
/** LOGICAL index name (a `data_indexes` key). See {@link SprigrDataSearchOpts.index}. */
|
|
977
|
+
index?: string;
|
|
784
978
|
}
|
|
785
979
|
/**
|
|
786
|
-
* The marketplace install's
|
|
787
|
-
*
|
|
788
|
-
*
|
|
789
|
-
*
|
|
980
|
+
* The marketplace install's per-company datastore on Sprigr search,
|
|
981
|
+
* injected on `env.SPRIGR` by the platform wrapper for /__sprigr/*
|
|
982
|
+
* dispatch.
|
|
983
|
+
*
|
|
984
|
+
* Single-index apps (manifest `data_index`) get the fixed
|
|
985
|
+
* `<companyId>-app-<slug>` index and never pass `index`. Multi-index apps
|
|
986
|
+
* (manifest `data_indexes`, a map of logical name -> config) pass
|
|
987
|
+
* `{ index: '<logical name>' }` on EVERY call; the platform derives the
|
|
988
|
+
* physical name(s) server-side, including per-year/per-month shards for
|
|
989
|
+
* logical indexes that declare `shard_by` + an immutable ISO-date
|
|
990
|
+
* `shard_field`. Every physical index is registered with the company at
|
|
991
|
+
* creation, so agents can discover it via `sprigr_list_my_indexes`.
|
|
790
992
|
*
|
|
791
993
|
* Intended pattern: the app syncs lean provider objects via `import`,
|
|
792
994
|
* its tool handlers call `search` to resolve a fuzzy human reference
|
|
@@ -796,30 +998,61 @@ interface SprigrDataSearchResult {
|
|
|
796
998
|
*/
|
|
797
999
|
interface SprigrDataApi {
|
|
798
1000
|
/**
|
|
799
|
-
* Upsert objects into the
|
|
800
|
-
*
|
|
801
|
-
* import from the manifest
|
|
802
|
-
*
|
|
1001
|
+
* Upsert objects into the store. Each object MUST carry a unique string
|
|
1002
|
+
* `objectID`. Max 1000 objects per call. Indexes are auto-created on
|
|
1003
|
+
* first import from the manifest settings; re-imports upsert by
|
|
1004
|
+
* objectID.
|
|
1005
|
+
*
|
|
1006
|
+
* Sharded logical indexes route each object by its `shard_field` date;
|
|
1007
|
+
* an object whose value is missing or not an ISO-8601 date string fails
|
|
1008
|
+
* the WHOLE batch (`shard_field_invalid`) — fix the data, don't guess.
|
|
1009
|
+
* The response's `physical_indexes` lists what was written per shard.
|
|
803
1010
|
*/
|
|
804
1011
|
import(objects: Array<{
|
|
805
1012
|
objectID: string;
|
|
806
1013
|
[key: string]: unknown;
|
|
807
|
-
}
|
|
1014
|
+
}>, opts?: SprigrDataIndexOpts): Promise<{
|
|
808
1015
|
ok: boolean;
|
|
809
1016
|
indexed: number;
|
|
810
1017
|
index: string;
|
|
1018
|
+
physical_indexes?: Array<{
|
|
1019
|
+
index: string;
|
|
1020
|
+
shard?: string;
|
|
1021
|
+
indexed: number;
|
|
1022
|
+
}>;
|
|
811
1023
|
}>;
|
|
812
1024
|
/**
|
|
813
|
-
* Search the
|
|
814
|
-
*
|
|
1025
|
+
* Search the store. A never-imported index yields an empty hit list,
|
|
1026
|
+
* not an error. Sharded logical indexes fan out across shards and merge
|
|
1027
|
+
* (nbHits sums, facet counts sum, aggregations merge exactly; without
|
|
1028
|
+
* `sortBy`, merged hits come newest shard first).
|
|
815
1029
|
*/
|
|
816
1030
|
search(opts?: SprigrDataSearchOpts): Promise<SprigrDataSearchResult>;
|
|
817
|
-
/**
|
|
818
|
-
|
|
1031
|
+
/**
|
|
1032
|
+
* Fetch one object by objectID. `object` is null when absent. On a
|
|
1033
|
+
* sharded logical index the platform probes shards newest-first.
|
|
1034
|
+
*/
|
|
1035
|
+
get(objectID: string, opts?: SprigrDataIndexOpts): Promise<{
|
|
819
1036
|
ok: boolean;
|
|
820
1037
|
object: Record<string, unknown> | null;
|
|
821
1038
|
index: string;
|
|
822
1039
|
}>;
|
|
1040
|
+
/**
|
|
1041
|
+
* Delete objects by objectID (mirror maintenance for source-side
|
|
1042
|
+
* deletions). Idempotent: unknown ids and never-imported indexes are a
|
|
1043
|
+
* no-op success. On a sharded logical index the delete fans out to
|
|
1044
|
+
* every shard. `?`-optional because older platform wrapper builds
|
|
1045
|
+
* predate it — feature-detect and degrade (never delete on
|
|
1046
|
+
* uncertainty), per the mirror-maintenance guidance in
|
|
1047
|
+
* docs/platform-reference.md.
|
|
1048
|
+
*/
|
|
1049
|
+
delete?(objectIDs: string[], opts?: SprigrDataIndexOpts & {
|
|
1050
|
+
withAcl?: boolean;
|
|
1051
|
+
}): Promise<{
|
|
1052
|
+
ok: boolean;
|
|
1053
|
+
deleted: number;
|
|
1054
|
+
index: string;
|
|
1055
|
+
}>;
|
|
823
1056
|
}
|
|
824
1057
|
/**
|
|
825
1058
|
* Durable app file storage exposed on `env.SPRIGR.files` by the platform
|
|
@@ -992,4 +1225,4 @@ interface ExecutionContextLike {
|
|
|
992
1225
|
}
|
|
993
1226
|
type HandlerFn<TArgs, TResult> = (args: TArgs, env: Record<string, unknown>, ctx?: ExecutionContextLike) => Promise<TResult>;
|
|
994
1227
|
|
|
995
|
-
export { type Actor, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type D1Like, type D1PreparedStatementLike, DEFAULT_EMIT_TIMEOUT_MS, DEFAULT_MAX_FILE_BYTES, type EmitMarketplaceEventOptions, type EmitResult, type EmitTransport, type EventArgs, type ExecutionContextLike, type FetchFileResult, type FetchWithRetryOptions, type GetAppFileResult, type HandlerFn, type InboxOwner, type InstallBridge, type MarketplaceEventEmitOpts, type MarketplaceEventSourceIntegration, type OAuthBouncerSignals, type OAuthState, type PlatformHostEnv, type PutAppFileArgs, type PutAppFileResult, type PutAppFileStreamArgs, type ScheduleArgs, type SprigrDataApi, type SprigrDataSearchOpts, type SprigrDataSearchResult, type SprigrFilesApi, type SprigrFilesApiWithEdit, type SprigrFilesCreateInput, type SprigrFilesCreateResult, type SprigrFilesEditInput, type SprigrFilesEditResult, type SprigrFilesJobResult, type WebhookArgs, type WfpBridgeEnv, type WfpEmitReply, actorKey, appFileUrl, base64ToBytes, buildMarketplaceWebhookUrl, bytesToBase64, canEmit, constantTimeEqual, createMarketplaceEmitter, decodeState, deleteAppFile, describeMissingBridge, emitMarketplaceEvent, encodeState, fetchFileAsBase64, fetchFileBytes, fetchWithRetry, getAppFile, hmacSha256Hex, installTokenPost, isStagingHost, listAppFiles, overlaySprigr, ownerFromActorKey, parseActor, putAppFile, putAppFileStream, randomHex, resolveInstallBridge, resolveOAuthBouncerBase, resolveOAuthBouncerCallbackUrl, resolvePlatformWebhookBase, withSprigrEmitFallback };
|
|
1228
|
+
export { type Actor, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type D1Like, type D1PreparedStatementLike, DEFAULT_EMIT_TIMEOUT_MS, DEFAULT_MAX_FILE_BYTES, type EmitMarketplaceEventOptions, type EmitResult, type EmitTransport, type EventArgs, type ExecutionContextLike, type FetchFileResult, type FetchWithRetryOptions, type GetAppFileResult, type HandlerFn, type InboxOwner, type InstallBridge, type MarketplaceEventEmitOpts, type MarketplaceEventSourceIntegration, NotConnectedError, type OAuthBouncerSignals, type OAuthState, type PlatformHostEnv, type PutAppFileArgs, type PutAppFileResult, type PutAppFileStreamArgs, type ScheduleArgs, type SprigrDataAggregationCell, type SprigrDataAggregationResult, type SprigrDataAggregationSpec, type SprigrDataApi, type SprigrDataIndexOpts, type SprigrDataSearchOpts, type SprigrDataSearchResult, type SprigrFilesApi, type SprigrFilesApiWithEdit, type SprigrFilesCreateInput, type SprigrFilesCreateResult, type SprigrFilesEditInput, type SprigrFilesEditResult, type SprigrFilesJobResult, type ToolResult, type ToolWrapperOptions, type WebhookArgs, type WfpBridgeEnv, type WfpEmitReply, actorKey, actorTool, appFileUrl, base64ToBytes, buildMarketplaceWebhookUrl, bytesToBase64, canEmit, constantTimeEqual, createMarketplaceEmitter, createToolWrappers, decodeState, deleteAppFile, describeMissingBridge, emitMarketplaceEvent, encodeState, fetchFileAsBase64, fetchFileBytes, fetchWithRetry, getAppFile, hmacSha256Hex, installTokenPost, isNotConnectedError, isStagingHost, listAppFiles, overlaySprigr, ownerFromActorKey, parseActor, putAppFile, putAppFileStream, randomHex, resolveInstallBridge, resolveOAuthBouncerBase, resolveOAuthBouncerCallbackUrl, resolvePlatformWebhookBase, tool, withSprigrEmitFallback };
|
package/dist/index.js
CHANGED
|
@@ -339,6 +339,66 @@ function ownerFromActorKey(key) {
|
|
|
339
339
|
return null;
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
// src/tool-wrappers.ts
|
|
343
|
+
var NotConnectedError = class extends Error {
|
|
344
|
+
constructor(message = "The calling actor has no connection with this app.") {
|
|
345
|
+
super(message);
|
|
346
|
+
this.name = "NotConnectedError";
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
function isNotConnectedError(err) {
|
|
350
|
+
if (err instanceof NotConnectedError) return true;
|
|
351
|
+
return err instanceof Error && err.name === "NotConnectedError";
|
|
352
|
+
}
|
|
353
|
+
var DEFAULT_NO_CALLER_HINT = "The platform did not stamp caller identity on this call. Retry from an agent context.";
|
|
354
|
+
var DEFAULT_NOT_CONNECTED_HINT = "You have not connected your own account with this app yet, and this tool only ever acts as the person calling it. Use the app's connect tool to get an authorize link.";
|
|
355
|
+
function shapeError(err, opts) {
|
|
356
|
+
const mapped = opts.mapError?.(err);
|
|
357
|
+
if (mapped) return { ok: false, ...mapped };
|
|
358
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
359
|
+
}
|
|
360
|
+
function tool(fn, opts = {}) {
|
|
361
|
+
return async (args, env) => {
|
|
362
|
+
try {
|
|
363
|
+
return { ok: true, result: await fn(env, args ?? {}) };
|
|
364
|
+
} catch (err) {
|
|
365
|
+
return shapeError(err, opts);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function actorTool(fn, opts = {}) {
|
|
370
|
+
return async (args, env) => {
|
|
371
|
+
const actor = parseActor(args);
|
|
372
|
+
if (!actor || !actor.platformUserId && !actor.agentId) {
|
|
373
|
+
return {
|
|
374
|
+
ok: false,
|
|
375
|
+
error: "no_caller_identity",
|
|
376
|
+
hint: opts.noCallerIdentityHint ?? DEFAULT_NO_CALLER_HINT,
|
|
377
|
+
status: 412
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
return { ok: true, result: await fn(env, actor, args ?? {}) };
|
|
382
|
+
} catch (err) {
|
|
383
|
+
if (isNotConnectedError(err)) {
|
|
384
|
+
return {
|
|
385
|
+
ok: false,
|
|
386
|
+
error: "not_connected",
|
|
387
|
+
hint: opts.notConnectedHint ?? DEFAULT_NOT_CONNECTED_HINT,
|
|
388
|
+
status: 412
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
return shapeError(err, opts);
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function createToolWrappers(opts = {}) {
|
|
396
|
+
return {
|
|
397
|
+
tool: (fn) => tool(fn, opts),
|
|
398
|
+
actorTool: (fn) => actorTool(fn, opts)
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
342
402
|
// src/app-files.ts
|
|
343
403
|
var DEFAULT_PLATFORM_BASE = "https://webhooks.sprigr.com";
|
|
344
404
|
function platformBase(env) {
|
|
@@ -428,7 +488,9 @@ function deleteAppFile(env, key) {
|
|
|
428
488
|
export {
|
|
429
489
|
DEFAULT_EMIT_TIMEOUT_MS,
|
|
430
490
|
DEFAULT_MAX_FILE_BYTES,
|
|
491
|
+
NotConnectedError,
|
|
431
492
|
actorKey,
|
|
493
|
+
actorTool,
|
|
432
494
|
appFileUrl,
|
|
433
495
|
base64ToBytes,
|
|
434
496
|
buildMarketplaceWebhookUrl,
|
|
@@ -436,6 +498,7 @@ export {
|
|
|
436
498
|
canEmit,
|
|
437
499
|
constantTimeEqual,
|
|
438
500
|
createMarketplaceEmitter,
|
|
501
|
+
createToolWrappers,
|
|
439
502
|
decodeState,
|
|
440
503
|
deleteAppFile,
|
|
441
504
|
describeMissingBridge,
|
|
@@ -447,6 +510,7 @@ export {
|
|
|
447
510
|
getAppFile,
|
|
448
511
|
hmacSha256Hex,
|
|
449
512
|
installTokenPost,
|
|
513
|
+
isNotConnectedError,
|
|
450
514
|
isStagingHost,
|
|
451
515
|
listAppFiles,
|
|
452
516
|
overlaySprigr,
|
|
@@ -459,5 +523,6 @@ export {
|
|
|
459
523
|
resolveOAuthBouncerBase,
|
|
460
524
|
resolveOAuthBouncerCallbackUrl,
|
|
461
525
|
resolvePlatformWebhookBase,
|
|
526
|
+
tool,
|
|
462
527
|
withSprigrEmitFallback
|
|
463
528
|
};
|