@collective-intelligence/external-apps-server-sdk 0.1.3 → 0.1.5
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 +149 -5
- package/dist/admin.d.ts +12 -0
- package/dist/auth/federated.d.ts +66 -0
- package/dist/auth/federated.js +1 -1
- package/dist/gateway.js +1 -1
- package/dist/index.d.ts +124 -0
- package/dist/index.js +1 -1
- package/dist/persistence/drizzle.js +1 -1
- package/dist/runtime/effect.js +1 -1
- package/dist/tools/ai-sdk.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -427,6 +427,54 @@ succeeded before the new turn started count. A turn is
|
|
|
427
427
|
subject, never with the application's scopes. Visitors are separate owners:
|
|
428
428
|
one visitor never reads another visitor's conversation.
|
|
429
429
|
|
|
430
|
+
## Messages
|
|
431
|
+
|
|
432
|
+
`messages.send` sends one message through a messaging connection of the
|
|
433
|
+
Organization, for example a Telegram bot or a WhatsApp Business Account. The
|
|
434
|
+
application needs the `messaging:send` scope, and an administrator must grant
|
|
435
|
+
the connection to the application on its administration page.
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
const sent = await ci.messages.send({
|
|
439
|
+
connectionId,
|
|
440
|
+
content: { kind: "text", text: "Your order is ready." },
|
|
441
|
+
idempotencyKey: `order-${orderId}-ready`,
|
|
442
|
+
organizationId,
|
|
443
|
+
target: { conversation, mode: "continue" }
|
|
444
|
+
})
|
|
445
|
+
// sent.value.result: { conversation, messageId }
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
- `target` continues a conversation with the Conversation Reference of an
|
|
449
|
+
earlier message (`{ mode: "continue", conversation }`), or starts one with
|
|
450
|
+
the recipient fields of the vendor (`{ mode: "start_new", recipient }`).
|
|
451
|
+
- `content` is a text, or `{ kind: "voice", text, voice }` for a text that CI
|
|
452
|
+
speaks.
|
|
453
|
+
- `sender` holds the sender fields of the vendor, for example
|
|
454
|
+
`{ phoneNumberId }`. It is required when the account has more than one
|
|
455
|
+
sender. `service` names the vendor service when the connection has more
|
|
456
|
+
than one.
|
|
457
|
+
- The result is the Message Receipt: the message id of the vendor and the
|
|
458
|
+
Conversation Reference that continues the conversation. It means that the
|
|
459
|
+
vendor accepted the message, not that a person received it.
|
|
460
|
+
- `idempotencyKey` (at most 256 characters; generated when omitted) makes a
|
|
461
|
+
repeat safe. A repeat with the same key and content returns the first
|
|
462
|
+
outcome and sends nothing. A repeat with the same key and other content
|
|
463
|
+
returns `conflict`. Use your own key when your server can repeat a send
|
|
464
|
+
after a crash.
|
|
465
|
+
- CI sends once and never retries a failed send. A failed send returns
|
|
466
|
+
`message_send_failed` with a `sendErrorCode`, for example `window_closed`,
|
|
467
|
+
`recipient_unreachable`, `template_required` or `transport`. The same key
|
|
468
|
+
returns the same failure: use a new key to send again.
|
|
469
|
+
- `send_outcome_unknown` means that the first send with the key has no
|
|
470
|
+
recorded outcome: it still runs, or CI stopped before it recorded the
|
|
471
|
+
outcome. CI does not send again for the key. Repeat the call later; if the
|
|
472
|
+
answer stays the same, check with the vendor before you send with a new key.
|
|
473
|
+
- `not_found` means that the connection does not exist or is not granted to
|
|
474
|
+
the application. `rate_limited` (HTTP 429) means that the connection
|
|
475
|
+
reached its rate limit; it is retryable, and the client repeats it with the
|
|
476
|
+
same key.
|
|
477
|
+
|
|
430
478
|
## Member users
|
|
431
479
|
|
|
432
480
|
A confidential application administers the `member` users of its own
|
|
@@ -503,11 +551,18 @@ const links = await ci.users.listCompanyLinks({ organizationId, userId })
|
|
|
503
551
|
- A delegated user's `/application/v1/resources` and `knowledge_*` calls, and
|
|
504
552
|
the Agents, Skills and workflows the user runs, carry the user's links to
|
|
505
553
|
Knowledge policy as `subject.linkedCompanyIds`, and a policy reads the
|
|
506
|
-
entity id as `resource` path `["id"]`.
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
554
|
+
entity id as `resource` path `["id"]`. CI also sets `subject.access`:
|
|
555
|
+
`"read"` for gets, lists, searches, facets, autocomplete, traversal and
|
|
556
|
+
retrieval, `"write"` for creates, updates, deletes and relation writes. The
|
|
557
|
+
operation sets it; a caller cannot. A policy that does not test `access`
|
|
558
|
+
applies to reads and writes alike.
|
|
559
|
+
- To let `member`-role users write only their own companies but read every
|
|
560
|
+
company (for example through member-run Agents), declare a `deny` `filter`
|
|
561
|
+
policy on the company schema: subject match `user.role` equals `member` and
|
|
562
|
+
`access` equals `write`, rule "entity id not in `linkedCompanyIds`". Updates
|
|
563
|
+
and deletes of any other company, and creates, then fail with `forbidden`;
|
|
564
|
+
gets, lists and searches still return it. Without the `access` clause the
|
|
565
|
+
same policy also hides other companies from reads. Other roles and service
|
|
511
566
|
credentials are unaffected.
|
|
512
567
|
- The policy binds an execution only while it acts as the member. An Agent or
|
|
513
568
|
Skill tool bound to a fixed user, or a workflow that runs as another user or
|
|
@@ -1124,6 +1179,12 @@ const auth = createFederatedAuth({
|
|
|
1124
1179
|
})
|
|
1125
1180
|
```
|
|
1126
1181
|
|
|
1182
|
+
For local development, `applicationOrigin` and `issuer` also accept an `http`
|
|
1183
|
+
origin whose host is exactly `localhost`, a `127.0.0.0/8` address, or `[::1]`,
|
|
1184
|
+
for example `http://localhost:3000`. On such an origin the session cookie is
|
|
1185
|
+
`ci-federated`, without the `__Host-` prefix and the `Secure` attribute, which
|
|
1186
|
+
browsers do not accept over plain HTTP. Any other `http` origin is rejected.
|
|
1187
|
+
|
|
1127
1188
|
Each request uses its own URL origin. A request on an unlisted origin gets
|
|
1128
1189
|
`400 invalid_request`, and session lookups on it return `null`. The `Origin`
|
|
1129
1190
|
header, `returnTo` and logout checks compare with that request origin. Login
|
|
@@ -1145,6 +1206,89 @@ session lookup revalidates the short-lived CI identity. Revoked or expired CI
|
|
|
1145
1206
|
access ends application authorization on the next validation. A confidential
|
|
1146
1207
|
OAuth client may supply `clientSecret`; a public PKCE client omits it.
|
|
1147
1208
|
|
|
1209
|
+
### Configure from the CLI enrollment
|
|
1210
|
+
|
|
1211
|
+
`ci apps enroll --env-file .env.local` writes the application's identity as
|
|
1212
|
+
`CI_ISSUER`, `CI_AUDIENCE`, `CI_APPLICATION_ID`, `CI_CLIENT_ID`,
|
|
1213
|
+
`CI_ORGANIZATION_ID`, `CI_SCOPES` and, for a confidential application,
|
|
1214
|
+
`CI_CLIENT_SECRET`. `readApplicationEnrollment` reads those variables, so the
|
|
1215
|
+
application names none of them. It throws a `TypeError` that lists every
|
|
1216
|
+
missing variable. Pass the same prefix as `--prefix` when it is not `CI_`.
|
|
1217
|
+
`ci apps env <application>` prints the same variables for an application that
|
|
1218
|
+
already exists, for example to copy them into a deployment pipeline; it leaves
|
|
1219
|
+
out `CI_CLIENT_SECRET`, which CI shows only once.
|
|
1220
|
+
|
|
1221
|
+
```ts
|
|
1222
|
+
import {
|
|
1223
|
+
createFederatedAuth,
|
|
1224
|
+
readApplicationEnrollment
|
|
1225
|
+
} from "@collective-intelligence/external-apps-server-sdk/auth/federated"
|
|
1226
|
+
|
|
1227
|
+
const enrollment = readApplicationEnrollment(process.env)
|
|
1228
|
+
export const auth = createFederatedAuth({
|
|
1229
|
+
...enrollment,
|
|
1230
|
+
applicationOrigin: "http://localhost:3000",
|
|
1231
|
+
session: { kind: "sealed_cookie", secrets: [sessionSecret] }
|
|
1232
|
+
})
|
|
1233
|
+
```
|
|
1234
|
+
|
|
1235
|
+
Register `<applicationOrigin>/api/auth/callback` as the redirect URI:
|
|
1236
|
+
`ci apps enroll --redirect-uri http://localhost:3000/api/auth/callback ...`.
|
|
1237
|
+
|
|
1238
|
+
### Read the session without a `Request`
|
|
1239
|
+
|
|
1240
|
+
`getSession` and `getAccessToken` take a `Request`. Next.js Server Components
|
|
1241
|
+
and Server Functions have headers only. `sessionRequest` builds the request
|
|
1242
|
+
those reads need: it carries only the cookie, on the application origin.
|
|
1243
|
+
|
|
1244
|
+
```ts
|
|
1245
|
+
import { sessionRequest } from "@collective-intelligence/external-apps-server-sdk/auth/federated"
|
|
1246
|
+
import { headers } from "next/headers"
|
|
1247
|
+
|
|
1248
|
+
const accessToken = await auth.getAccessToken?.(
|
|
1249
|
+
sessionRequest(await headers(), applicationOrigin)
|
|
1250
|
+
)
|
|
1251
|
+
```
|
|
1252
|
+
|
|
1253
|
+
Mount `auth.handler` as the `GET` and `POST` handlers of
|
|
1254
|
+
`app/api/auth/[...path]/route.ts`.
|
|
1255
|
+
|
|
1256
|
+
### Verify the access token in your own API
|
|
1257
|
+
|
|
1258
|
+
When the application forwards the user's CI access token to a separate API as
|
|
1259
|
+
`Authorization: Bearer`, that API checks it with `createAccessTokenVerifier`.
|
|
1260
|
+
|
|
1261
|
+
```ts
|
|
1262
|
+
import {
|
|
1263
|
+
createAccessTokenVerifier,
|
|
1264
|
+
FederatedAuthRejectedError,
|
|
1265
|
+
readApplicationEnrollment
|
|
1266
|
+
} from "@collective-intelligence/external-apps-server-sdk/auth/federated"
|
|
1267
|
+
|
|
1268
|
+
const tokens = createAccessTokenVerifier(readApplicationEnrollment(process.env))
|
|
1269
|
+
|
|
1270
|
+
const caller = await tokens.verify(bearerToken)
|
|
1271
|
+
// userId, organizationId, applicationId, clientId, scopes, sessionId, expiresAt
|
|
1272
|
+
|
|
1273
|
+
const identity = await tokens.identity(bearerToken, { signal: request.signal })
|
|
1274
|
+
// the same fields, and user { id, email, name }, roles, groupIds
|
|
1275
|
+
```
|
|
1276
|
+
|
|
1277
|
+
- `verify` is offline against the issuer key set: ES256 signature, issuer,
|
|
1278
|
+
audience, lifetime, and the application, client and Organization you supply.
|
|
1279
|
+
It does not see a revocation before the token expires.
|
|
1280
|
+
- `identity` runs `verify`, then reads CI's current identity for the token and
|
|
1281
|
+
checks that it describes the same grant. Use it when the API needs the
|
|
1282
|
+
user's roles or groups, or must stop at once when CI revokes the token. It
|
|
1283
|
+
is one CI request for each call, with a 10-second limit; cache the result
|
|
1284
|
+
for as long as a role change may take to reach your API. Pass the `signal`
|
|
1285
|
+
of the request you serve: when it aborts, the CI request ends and the call
|
|
1286
|
+
rejects with the abort reason.
|
|
1287
|
+
|
|
1288
|
+
Both throw `FederatedAuthRejectedError` for a token that is not valid for this
|
|
1289
|
+
application; answer `401`. Both throw `ApplicationUserAuthUnavailableError`
|
|
1290
|
+
when CI does not answer; answer `503`.
|
|
1291
|
+
|
|
1148
1292
|
### Forwarding a bound entity to CI
|
|
1149
1293
|
|
|
1150
1294
|
Import `defineCiEntityResource` from `/resources` and use it with the same
|
package/dist/admin.d.ts
CHANGED
|
@@ -4965,6 +4965,7 @@ declare const createAdministrationApi: (client: Client$1) => {
|
|
|
4965
4965
|
description: string | null;
|
|
4966
4966
|
directChildCount: number;
|
|
4967
4967
|
directMemberCount: number;
|
|
4968
|
+
fqn: string | null;
|
|
4968
4969
|
id: string;
|
|
4969
4970
|
inheritedMembershipImpact: {
|
|
4970
4971
|
descendantGroupCount: number;
|
|
@@ -4973,6 +4974,11 @@ declare const createAdministrationApi: (client: Client$1) => {
|
|
|
4973
4974
|
};
|
|
4974
4975
|
name: string;
|
|
4975
4976
|
organizationId: string;
|
|
4977
|
+
package: {
|
|
4978
|
+
fqn: string;
|
|
4979
|
+
managed: boolean;
|
|
4980
|
+
readonly: boolean;
|
|
4981
|
+
} | null;
|
|
4976
4982
|
parentGroupId: string | null;
|
|
4977
4983
|
parentPath: Array<{
|
|
4978
4984
|
id: string;
|
|
@@ -5001,6 +5007,7 @@ declare const createAdministrationApi: (client: Client$1) => {
|
|
|
5001
5007
|
description: string | null;
|
|
5002
5008
|
directChildCount: number;
|
|
5003
5009
|
directMemberCount: number;
|
|
5010
|
+
fqn: string | null;
|
|
5004
5011
|
id: string;
|
|
5005
5012
|
inheritedMembershipImpact: {
|
|
5006
5013
|
descendantGroupCount: number;
|
|
@@ -5009,6 +5016,11 @@ declare const createAdministrationApi: (client: Client$1) => {
|
|
|
5009
5016
|
};
|
|
5010
5017
|
name: string;
|
|
5011
5018
|
organizationId: string;
|
|
5019
|
+
package: {
|
|
5020
|
+
fqn: string;
|
|
5021
|
+
managed: boolean;
|
|
5022
|
+
readonly: boolean;
|
|
5023
|
+
} | null;
|
|
5012
5024
|
parentGroupId: string | null;
|
|
5013
5025
|
parentPath: Array<{
|
|
5014
5026
|
id: string;
|
package/dist/auth/federated.d.ts
CHANGED
|
@@ -57,6 +57,67 @@ export type UserAuthScopePolicy = Readonly<{
|
|
|
57
57
|
writeScope: string;
|
|
58
58
|
}>;
|
|
59
59
|
export type UserAuthAuthorization = (resourceId: string) => (request: Request, operation?: string) => Promise<EntityInvocationContext | Response>;
|
|
60
|
+
export type CiFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
61
|
+
export type AccessTokenVerifierOptions = Readonly<{
|
|
62
|
+
/** CI External App id (`app_id` claim). Give this, `clientId`, or both. */
|
|
63
|
+
applicationId?: string;
|
|
64
|
+
audience: string;
|
|
65
|
+
/** OAuth client id (`client_id` claim). Give this, `applicationId`, or both. */
|
|
66
|
+
clientId?: string;
|
|
67
|
+
fetch?: CiFetch;
|
|
68
|
+
/** HTTPS CI issuer, or a loopback HTTP issuer for local development. */
|
|
69
|
+
issuer: string;
|
|
70
|
+
now?: () => number;
|
|
71
|
+
/** When set, a token of another Organization is rejected. */
|
|
72
|
+
organizationId?: string;
|
|
73
|
+
}>;
|
|
74
|
+
export type VerifiedAccessToken = Readonly<{
|
|
75
|
+
applicationId: string;
|
|
76
|
+
clientId: string;
|
|
77
|
+
expiresAt: Date;
|
|
78
|
+
organizationId: string;
|
|
79
|
+
scopes: readonly string[];
|
|
80
|
+
sessionId: string;
|
|
81
|
+
userId: string;
|
|
82
|
+
}>;
|
|
83
|
+
export type AccessTokenIdentity = VerifiedAccessToken & Readonly<{
|
|
84
|
+
/** Absent when CI supplies no current group policy for the application. */
|
|
85
|
+
groupIds: readonly string[] | undefined;
|
|
86
|
+
/** CI Organization roles; absent when CI supplies no current role policy. */
|
|
87
|
+
roles: readonly string[] | undefined;
|
|
88
|
+
user: Readonly<{
|
|
89
|
+
email: string | null;
|
|
90
|
+
id: string;
|
|
91
|
+
name: string | null;
|
|
92
|
+
}>;
|
|
93
|
+
}>;
|
|
94
|
+
export type AccessTokenVerifier = Readonly<{
|
|
95
|
+
/**
|
|
96
|
+
* The token's current CI identity: `verify`, then CI's own answer for the
|
|
97
|
+
* user, roles and groups. A token CI has revoked is rejected. Pass the
|
|
98
|
+
* `signal` of the request being served: when it aborts, the CI request ends
|
|
99
|
+
* and the call rejects with the abort reason.
|
|
100
|
+
*/
|
|
101
|
+
identity: (token: string, options?: Readonly<{
|
|
102
|
+
signal?: AbortSignal;
|
|
103
|
+
}>) => Promise<AccessTokenIdentity>;
|
|
104
|
+
/**
|
|
105
|
+
* Offline check against the issuer key set. It does not see a revocation
|
|
106
|
+
* before the token expires.
|
|
107
|
+
*/
|
|
108
|
+
verify: (token: string) => Promise<VerifiedAccessToken>;
|
|
109
|
+
}>;
|
|
110
|
+
/** The application's CI enrollment, as `ci apps enroll --env-file` writes it. */
|
|
111
|
+
export type ApplicationEnrollment = Readonly<{
|
|
112
|
+
applicationId: string;
|
|
113
|
+
audience: string;
|
|
114
|
+
clientId: string;
|
|
115
|
+
/** Present for a confidential application only. */
|
|
116
|
+
clientSecret?: string;
|
|
117
|
+
issuer: string;
|
|
118
|
+
organizationId: string;
|
|
119
|
+
scopes: readonly string[];
|
|
120
|
+
}>;
|
|
60
121
|
declare class FederatedAuthRejectedError extends Error {
|
|
61
122
|
readonly code: "invalid_request" | "invalid_state" | "invalid_identity" | "authorization_failed";
|
|
62
123
|
constructor(code: FederatedAuthRejectedError["code"]);
|
|
@@ -104,6 +165,8 @@ export type FederatedAuthOptions = Readonly<{
|
|
|
104
165
|
/**
|
|
105
166
|
* HTTPS browser-visible origin, or list of origins, that owns the auth routes and __Host cookie.
|
|
106
167
|
* Each request uses its own listed origin; register `<origin><basePath>/callback` for each one.
|
|
168
|
+
* A loopback HTTP origin such as `http://localhost:3000` is accepted for local development;
|
|
169
|
+
* its cookie has no `__Host-` prefix and no `Secure` attribute.
|
|
107
170
|
*/
|
|
108
171
|
applicationOrigin: string | readonly string[];
|
|
109
172
|
audience: string;
|
|
@@ -125,8 +188,11 @@ declare const FederatedAuthRejectedError$1: typeof FederatedAuthRejectedError;
|
|
|
125
188
|
type FederatedAuthRejectedError$1 = FederatedAuthRejectedError;
|
|
126
189
|
declare const ApplicationUserAuthUnavailableError$2: typeof ApplicationUserAuthUnavailableError;
|
|
127
190
|
export declare const authorizeWithUserAuth: (auth: Pick<ApplicationUserAuth, "getEntityContext">, policy: UserAuthScopePolicy) => UserAuthAuthorization;
|
|
191
|
+
export declare const createAccessTokenVerifier: (options: AccessTokenVerifierOptions) => AccessTokenVerifier;
|
|
128
192
|
export declare const createFederatedAuth: (options: FederatedAuthOptions) => ApplicationUserAuth;
|
|
129
193
|
declare const FederatedAuthRejectedError$2: typeof FederatedAuthRejectedError;
|
|
194
|
+
export declare const readApplicationEnrollment: (env: Readonly<Record<string, string | undefined>>, prefix?: string) => ApplicationEnrollment;
|
|
195
|
+
export declare const sessionRequest: (headers: Headers, applicationOrigin: string) => Request;
|
|
130
196
|
type ApplicationUserAuthUnavailableError$2 = ApplicationUserAuthUnavailableError$1;
|
|
131
197
|
type FederatedAuthRejectedError$2 = FederatedAuthRejectedError$1;
|
|
132
198
|
|