@deslop/trips 0.1.1249 → 0.1.1256

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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/dist/server.js +221 -24
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -4,9 +4,9 @@ Your flights, trains and stays, with live gates, delays and platforms, for the T
4
4
 
5
5
  ```bash
6
6
  npm install --global @deslop/trips
7
- trips service install --url https://trips.mp281x.xyz --auth https://auth.mp281x.xyz --apns-key-id <id> --apns-team-id <team>
7
+ trips service install --url https://trips.mp281x.xyz --auth https://auth.mp281x.xyz --gateway https://gateway.mp281x.xyz --apns-key-id <id> --apns-team-id <team>
8
8
  ```
9
9
 
10
- - **Secrets** in `~/.deslop/trips.env`: `AEROAPI_KEY`, `ANTHROPIC_API_KEY`, `APNS_KEY`.
10
+ - **Secrets** in `~/.deslop/trips.env`: `AEROAPI_KEY`, `APNS_KEY`; AI keys live in Gateway.
11
11
 
12
12
  Common flags and service commands: [Deslop app runtime](https://github.com/MP281X/deslop/blob/main/packages/runtime/README.md).
package/dist/server.js CHANGED
@@ -5957,6 +5957,36 @@ const has$4 = /*#__PURE__*/ dual(2, (self, key) => Object.hasOwn(self, key));
5957
5957
  */
5958
5958
  const get$11 = /*#__PURE__*/ dual(2, (self, key) => Object.hasOwn(self, key) ? some$1(self[key]) : none());
5959
5959
  /**
5960
+ * Removes a key from a record.
5961
+ *
5962
+ * **When to use**
5963
+ *
5964
+ * Use to create a shallow copy of a record without one property.
5965
+ *
5966
+ * **Details**
5967
+ *
5968
+ * If the key is not present, the result is still a shallow copy of the original
5969
+ * record.
5970
+ *
5971
+ * **Example** (Removing a key)
5972
+ *
5973
+ * ```ts import.meta.vitest
5974
+ * import { Record } from "effect"
5975
+ *
5976
+ * Record.remove({ a: 1, b: 2 }, "a") // => { b: 2 }
5977
+ * ```
5978
+ *
5979
+ * @stability stable
5980
+ * @category mutations
5981
+ * @since 2.0.0
5982
+ */
5983
+ const remove$6 = /*#__PURE__*/ dual(2, (self, key) => {
5984
+ if (!has$4(self, key)) return { ...self };
5985
+ const out = { ...self };
5986
+ delete out[key];
5987
+ return out;
5988
+ });
5989
+ /**
5960
5990
  * Maps a record into another record by applying a transformation function to each of its values.
5961
5991
  *
5962
5992
  * **Example** (Mapping record values)
@@ -34849,6 +34879,8 @@ function decodeUnknownEffect$1(schema, options) {
34849
34879
  function decodeUnknownOption$1(schema, options) {
34850
34880
  return asOption(decodeUnknownEffect$1(schema, options));
34851
34881
  }
34882
+ /** @internal */
34883
+ const decodeOption$1 = decodeUnknownOption$1;
34852
34884
  /**
34853
34885
  * Creates an effectful encoder for `unknown` input.
34854
34886
  *
@@ -37366,6 +37398,33 @@ const decodeEffect = decodeUnknownEffect;
37366
37398
  */
37367
37399
  const decodeUnknownOption = decodeUnknownOption$1;
37368
37400
  /**
37401
+ * Decodes a typed input (the schema's `Encoded` type) against a schema,
37402
+ * returning an `Option` that is `Some` with the decoded value on success or
37403
+ * `None` for schema mismatches.
37404
+ *
37405
+ * **When to use**
37406
+ *
37407
+ * Use when you already have input typed as the schema's `Encoded` type and
37408
+ * only need to know whether decoding succeeded.
37409
+ *
37410
+ * **Details**
37411
+ *
37412
+ * For `unknown` input use {@link decodeUnknownOption}.
37413
+ * Options may be provided either when creating the decoder or when applying it;
37414
+ * application options override creation options.
37415
+ *
37416
+ * **Gotchas**
37417
+ *
37418
+ * Only causes made entirely of schema issues are converted to `None`. Causes
37419
+ * that contain defects, interruptions, or other non-schema reasons throw
37420
+ * instead.
37421
+ *
37422
+ * @stability stable
37423
+ * @category decoding
37424
+ * @since 3.10.0
37425
+ */
37426
+ const decodeOption = decodeOption$1;
37427
+ /**
37369
37428
  * Decodes an `unknown` input against a schema synchronously, returning the
37370
37429
  * decoded value or throwing a {@link SchemaError} for schema mismatches.
37371
37430
  *
@@ -39697,6 +39756,25 @@ function Redacted$1(value$7, options) {
39697
39756
  });
39698
39757
  return make$56(schema.ast, { value: value$7 });
39699
39758
  }
39759
+ /**
39760
+ * Decodes a value and wraps it in `Redacted<A>`. Unlike {@link Redacted} which
39761
+ * expects the input to already be a `Redacted` instance, this schema decodes
39762
+ * the raw value and wraps it.
39763
+ *
39764
+ * @see {@link Redacted} for schemas whose input is already a `Redacted` value.
39765
+ * @stability stable
39766
+ * @category schemas
39767
+ * @since 4.0.0
39768
+ */
39769
+ function RedactedFromValue(value$8, options) {
39770
+ return decodeTo(Redacted$1(toType(value$8), {
39771
+ label: options?.label,
39772
+ disallowJsonEncode: options?.disallowEncode
39773
+ }), {
39774
+ decode: transform$4((t) => make$61(t, { label: options?.label })),
39775
+ encode: options?.disallowEncode ? forbidden((oe) => "Cannot encode Redacted" + (isSome(oe) && typeof oe.value.label === "string" ? ` with label: "${oe.value.label}"` : "")) : transform$4(value)
39776
+ })(value$8);
39777
+ }
39700
39778
  const immerable = /*#__PURE__*/ globalThis.Symbol.for("immer-draftable");
39701
39779
  const payloadToken = {};
39702
39780
  function makeClass(Inherited, identifier, struct$1, annotations, proto) {
@@ -51476,6 +51554,14 @@ const endSpanWithStatusError = (fiber, span, request, response) => {
51476
51554
  */
51477
51555
  const mapRequest = /*#__PURE__*/ dual(2, (self, f) => makeWith(self.postprocess, (request) => map$5(self.preprocess(request), f)));
51478
51556
  /**
51557
+ * Appends an effectful transformation of the request object before sending it.
51558
+ *
51559
+ * @stability unstable
51560
+ * @category mapping
51561
+ * @since 4.0.0
51562
+ */
51563
+ const mapRequestEffect = /*#__PURE__*/ dual(2, (self, f) => makeWith(self.postprocess, (request) => flatMap$3(self.preprocess(request), f)));
51564
+ /**
51479
51565
  * Retries common transient errors, such as rate limiting, timeouts or network issues.
51480
51566
  *
51481
51567
  * **When to use**
@@ -78537,6 +78623,29 @@ var AiError = class extends (/*#__PURE__*/ Error$1("effect/ai/AiError/AiError")(
78537
78623
  }
78538
78624
  };
78539
78625
  /**
78626
+ * Type guard to check if a value is an `AiError`.
78627
+ *
78628
+ * **Example** (Checking for an AI error)
78629
+ *
78630
+ * ```ts import.meta.vitest
78631
+ * import { AiError } from "effect/ai"
78632
+ *
78633
+ * const someError = new Error("generic error")
78634
+ * const aiError = AiError.make({
78635
+ * module: "Test",
78636
+ * method: "example",
78637
+ * reason: new AiError.RateLimitError({})
78638
+ * })
78639
+ *
78640
+ * const result = [AiError.isAiError(someError), AiError.isAiError(aiError)] // => [false, true]
78641
+ * ```
78642
+ *
78643
+ * @stability unstable
78644
+ * @category guards
78645
+ * @since 4.0.0
78646
+ */
78647
+ const isAiError = (u) => hasProperty(u, TypeId$4);
78648
+ /**
78540
78649
  * Type guard to check if a value is an `AiErrorReason`.
78541
78650
  *
78542
78651
  * **Example** (Checking for an AI error reason)
@@ -83106,6 +83215,78 @@ function failureOptions(error, status) {
83106
83215
  }
83107
83216
  var CurrentUser = class extends Service$1()("@deslop/runtime/auth/lib/utils/CurrentUser") {};
83108
83217
  var RpcAuth = class extends Service()("@deslop/runtime/auth/lib/utils/RpcAuth", { error: AuthError }) {};
83218
+ Literals([
83219
+ "anthropic",
83220
+ "openrouter",
83221
+ "opencode-zen",
83222
+ "opencode-go"
83223
+ ]);
83224
+ // ../../packages/runtime/src/ai/service.ts
83225
+ var AiUnavailable = class extends TaggedError()("AiUnavailable", { message: String$6 }) {};
83226
+ const Refusal = fromJsonString(Struct({ error: Struct({ message: String$6 }) }));
83227
+ function aiFailure(cause) {
83228
+ if (is(AiUnavailable)(cause)) return some$1(cause.message);
83229
+ if (!isAiError(cause)) return none();
83230
+ if (cause.reason._tag === "NetworkError") return some$1(cause.reason.description ?? cause.reason.message);
83231
+ if (cause.reason._tag === "UnknownError" && cause.reason.http?.body !== void 0) return map$11(decodeOption(Refusal)(cause.reason.http.body), (body) => body.error.message);
83232
+ return some$1(cause.reason.message);
83233
+ }
83234
+ var AiGateway = class AiGateway extends Service$1()("@deslop/runtime/ai/service/AiGateway") {
83235
+ static layer = effect(this, gen(function* () {
83236
+ const gateway = yield* option(String$5("GATEWAY_URL"));
83237
+ const local = yield* localAuth;
83238
+ const app = yield* String$5("APP_URL");
83239
+ const client = yield* HttpClient;
83240
+ const store = toSchemaStore(yield* KeyValueStore, Record$1(String$6, RedactedFromValue(String$6)));
83241
+ const grants = yield* make$42(getOrElse(yield* store.get("ai-gateway-grants"), () => ({})));
83242
+ const lock = yield* make$66(1);
83243
+ return AiGateway.of({
83244
+ apiUrl: (provider) => match$9(gateway, {
83245
+ onNone: () => fail$6(AiUnavailable.make({ message: "Start the app with --gateway" })),
83246
+ onSome: (url) => succeed$5(`${url}/ai/${provider}${match$1(provider === "openrouter", {
83247
+ onFalse: () => "",
83248
+ onTrue: () => "/v1"
83249
+ })}`)
83250
+ }),
83251
+ authorize: (userId) => (http) => pipe(mapRequestEffect(http, (request) => gen(function* () {
83252
+ if (local) return request;
83253
+ const token = (yield* get$2(grants))[userId];
83254
+ if (token === void 0) return yield* new HttpClientError({ reason: new TransportError({
83255
+ description: "AI access is unavailable; sign in again to enroll with Gateway",
83256
+ request
83257
+ }) });
83258
+ return setHeader$1(request, "authorization", `Bearer ${value(token)}`);
83259
+ })), tap((response) => pipe(gen(function* () {
83260
+ if (local || response.headers["x-gateway-grant-refused"] === void 0 || !exists(gateway, (url) => startsWith(`${url}/ai/`)(response.request.url))) return;
83261
+ const current = yield* get$2(grants);
83262
+ const token = current[userId];
83263
+ if (token === void 0 || response.request.headers["authorization"] !== `Bearer ${value(token)}`) return;
83264
+ const next = remove$6(current, userId);
83265
+ yield* pipe(store.set("ai-gateway-grants", next), ignore$1({
83266
+ log: "Warn",
83267
+ message: "The rejected AI grant could not be removed from storage"
83268
+ }));
83269
+ yield* set(grants, next);
83270
+ }), withPermit(lock)))),
83271
+ enroll: fnUntraced(function* (userId, headers) {
83272
+ return yield* pipe(gen(function* () {
83273
+ if (local || isNone(gateway) || (yield* get$2(grants))[userId] !== void 0) return;
83274
+ const response = yield* client.execute(yield* bodyJson(post(`${getOrElse(gateway, () => "")}/ai/grants`, { headers: match$1(headers["authorization"] !== void 0, {
83275
+ onFalse: () => ({ cookie: headers["cookie"] ?? "" }),
83276
+ onTrue: () => ({ authorization: headers["authorization"] })
83277
+ }) }), { app }));
83278
+ const result = yield* schemaBodyJson(Struct({ token: String$6 }))(response);
83279
+ const next = {
83280
+ ...yield* get$2(grants),
83281
+ [userId]: make$61(result.token)
83282
+ };
83283
+ yield* store.set("ai-gateway-grants", next);
83284
+ yield* set(grants, next);
83285
+ }), withPermit(lock), timeout$1("10 seconds"), catch_$2(() => logWarning("AI Gateway enrollment failed")));
83286
+ })
83287
+ });
83288
+ }));
83289
+ };
83109
83290
  // ../../packages/runtime/src/auth/service.ts
83110
83291
  function rejected(message) {
83111
83292
  return mapError$2((cause) => {
@@ -83124,12 +83305,18 @@ function rejected(message) {
83124
83305
  function developer() {
83125
83306
  return succeed$5(DEVELOPER);
83126
83307
  }
83308
+ function enrolled(user, headers) {
83309
+ return gen(function* () {
83310
+ const gateway = getOrUndefined(yield* context(), AiGateway);
83311
+ if (gateway !== void 0) yield* gateway.enroll(user.id, headers);
83312
+ });
83313
+ }
83127
83314
  const unavailable = text$1("The authorization service is temporarily unavailable; retry later", {
83128
83315
  headers: { "retry-after": "5" },
83129
83316
  status: 503
83130
83317
  });
83131
83318
  var Auth = class Auth extends Service$1()("@deslop/runtime/auth/service/Auth") {
83132
- static makeContext = (auth) => pipe(make$76(Auth, auth), add$1(RpcAuth, RpcAuth.of((effect, request) => pipe(auth.user(request.headers), tap$2(() => {
83319
+ static makeContext = (auth) => pipe(make$76(Auth, auth), add$1(RpcAuth, RpcAuth.of((effect, request) => pipe(auth.user(request.headers), tap$2((user) => enrolled(user, request.headers)), tap$2(() => {
83133
83320
  if (isStreamSchema(request.rpc.successSchema)) return annotateCurrentSpan("rpc.stream", true);
83134
83321
  return void_$1;
83135
83322
  }), flatMap$3((user) => raceFirst(provideService$2(effect, CurrentUser, user), pipe(delay(auth.user(request.headers), "5 minutes"), retry({ while: (error) => error.error === "temporarily_unavailable" }), forever)))))));
@@ -83164,6 +83351,7 @@ middleware()(gen(function* () {
83164
83351
  if (user.failure.error === "temporarily_unavailable") return unavailable;
83165
83352
  return text$1("Sign in first", { status: 401 });
83166
83353
  }
83354
+ yield* enrolled(user.success, (yield* HttpServerRequest).headers);
83167
83355
  return yield* provideService$2(app, CurrentUser, user.success);
83168
83356
  });
83169
83357
  }));
@@ -83546,6 +83734,11 @@ const SETTINGS = {
83546
83734
  flag: "auth",
83547
83735
  schema: SecureOrigin
83548
83736
  },
83737
+ GATEWAY_URL: {
83738
+ description: "Deslop Gateway origin for AI calls",
83739
+ flag: "gateway",
83740
+ schema: Origin
83741
+ },
83549
83742
  HOST: {
83550
83743
  description: `Listen address; defaults to ${DEFAULT_HOST}`,
83551
83744
  flag: "host",
@@ -83577,7 +83770,7 @@ function server(app) {
83577
83770
  return pipe(serve(pipe(mergeAll$2(app.routes, layerRoutes, layerIcon(app.icon), layerWeb, layerAnnounce), provide$2(match$9(fromUndefinedOr(app.middleware), {
83578
83771
  onNone: () => empty$13,
83579
83772
  onSome: (middleware$1) => middleware()(middleware$1).layer
83580
- })), provide$2(Auth.layer)), {
83773
+ })), provide$2(Auth.layer), provideMerge(pipe(AiGateway.layer, provide$2(layerDataStore)))), {
83581
83774
  disableLogger: true,
83582
83775
  middleware: guard
83583
83776
  }), provide$2(mergeAll$2(layerProtocol, match$1(app.telemetry === false, {
@@ -93071,15 +93264,22 @@ function bounded(text) {
93071
93264
  }
93072
93265
  var Extractor = class Extractor extends Service$1()("@deslop/trips/services/bookings/lib/extraction/Extractor") {
93073
93266
  static layer = effect(this, gen(function* () {
93074
- const key = yield* option(Redacted("ANTHROPIC_API_KEY"));
93075
- const model = yield* pipe(make({
93076
- config: {
93077
- max_tokens: 16e3,
93078
- output_config: { effort: "high" }
93079
- },
93080
- model: "claude-haiku-5-5"
93081
- }), provideServiceEffect(AnthropicClient, make$1({ apiKey: getOrUndefined$1(key) })));
93082
- const generate = fn("Extractor.generate")(function* (source) {
93267
+ const gateway = yield* AiGateway;
93268
+ const http = yield* HttpClient;
93269
+ const model = fnUntraced(function* (userId) {
93270
+ return yield* pipe(make({
93271
+ config: {
93272
+ max_tokens: 16e3,
93273
+ output_config: { effort: "high" }
93274
+ },
93275
+ model: "claude-haiku-5-5"
93276
+ }), provideServiceEffect(AnthropicClient, pipe(make$1({
93277
+ apiUrl: yield* gateway.apiUrl("anthropic"),
93278
+ transformClient: gateway.authorize(userId)
93279
+ }), provideService$2(HttpClient, http))));
93280
+ });
93281
+ return Extractor.of({ read: fn("Extractor.read")(function* (userId, source) {
93282
+ const anthropic = yield* model(userId);
93083
93283
  const today = `Today: ${formatIsoDateUtc(makeUnsafe$3(yield* currentTimeMillis))}`;
93084
93284
  function content() {
93085
93285
  if (hasProperty(source, "image")) return [{
@@ -93092,7 +93292,7 @@ var Extractor = class Extractor extends Service$1()("@deslop/trips/services/book
93092
93292
  }];
93093
93293
  return `${today}\n\n${bounded(source.text)}`;
93094
93294
  }
93095
- return (yield* model.generateObject({
93295
+ return (yield* anthropic.generateObject({
93096
93296
  prompt: [{
93097
93297
  content: INSTRUCTIONS,
93098
93298
  role: "system"
@@ -93102,16 +93302,13 @@ var Extractor = class Extractor extends Service$1()("@deslop/trips/services/book
93102
93302
  }],
93103
93303
  schema: Extraction
93104
93304
  })).value.items;
93105
- }, timeout$1("3 minutes"), retry({ times: 1 }), mapError$2((cause) => BookingsError.make({
93305
+ }, timeout$1("3 minutes"), retry({
93306
+ times: 1,
93307
+ while: (cause) => !is(AiUnavailable)(cause)
93308
+ }), mapError$2((cause) => BookingsError.make({
93106
93309
  cause: make$61(cause),
93107
- message: "The AI could not read the booking."
93108
- })));
93109
- return Extractor.of({ read: fn("Extractor.read")(function* (source) {
93110
- return yield* match$9(key, {
93111
- onNone: () => fail$6(BookingsError.make({ message: "The server has no AI key to read bookings." })),
93112
- onSome: () => generate(source)
93113
- });
93114
- }) });
93310
+ message: getOrElse(aiFailure(cause), () => "The AI could not read the booking.")
93311
+ }))) });
93115
93312
  }));
93116
93313
  };
93117
93314
  // src/services/bookings/lib/utils.ts
@@ -93819,8 +94016,8 @@ var Bookings = class Bookings extends Service$1()("@deslop/trips/services/bookin
93819
94016
  return make$75(void 0, [...current, booking]);
93820
94017
  });
93821
94018
  }),
93822
- save: fn("Bookings.save")(function* (source) {
93823
- const items = yield* extractor.read(source);
94019
+ save: fn("Bookings.save")(function* (userId, source) {
94020
+ const items = yield* extractor.read(userId, source);
93824
94021
  if (isReadonlyArrayEmpty(items)) return yield* BookingsError.make({ message: "No booking found. Copy a confirmation or boarding pass and try again." });
93825
94022
  const resolved = yield* forEach$1(items, (item) => result(resolve(item)), { concurrency: 1 });
93826
94023
  const issues = getFailures(resolved);
@@ -121874,7 +122071,7 @@ runApp({
121874
122071
  "bookings.refresh": (input) => tracker.refresh(input.id),
121875
122072
  "bookings.remove": (input) => bookings.remove(input.id),
121876
122073
  "bookings.restore": (input) => bookings.restore(input),
121877
- "bookings.save": (input) => bookings.save(input),
122074
+ "bookings.save": (input) => flatMap$3(CurrentUser, (user) => bookings.save(user.id, input)),
121878
122075
  "bookings.watch": () => bookings.events,
121879
122076
  "devices.register": (input) => push.register(input)
121880
122077
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deslop/trips",
3
- "version": "0.1.1249",
3
+ "version": "0.1.1256",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/MP281X/deslop.git",
@@ -30,5 +30,5 @@
30
30
  "engines": {
31
31
  "node": ">=26"
32
32
  },
33
- "fingerprint": "65423ef948406ff5bee8b891f295a67c3799d24c02475a58ea64ae7b131685b8"
33
+ "fingerprint": "72cb3e330ae400d6eed04c6308b0f230fd667441197fd59f248740abe95c20a1"
34
34
  }