@dereekb/openrouter 14.1.0 → 14.3.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 CHANGED
@@ -46,6 +46,79 @@ one run onto another's history.
46
46
  Short calls skip all of it: `callModelForPrompt(...)` runs inline and returns the result with no
47
47
  document.
48
48
 
49
+ ## Managing prompts
50
+
51
+ There is deliberately no Angular UI for prompt authoring. Declaring the CRUD is what makes every prompt
52
+ operation reachable over a consuming app's existing callModel surface — its CLI and the callModel MCP —
53
+ instead of requiring a screen.
54
+
55
+ | Operation | What it does |
56
+ |---|---|
57
+ | `openRouterPrompt.read` | The prompt plus **the version it actually serves**. |
58
+ | `openRouterPrompt.query` | Page the collection, optionally filtered by lifecycle state. |
59
+ | `openRouterPrompt.update` | Metadata, lifecycle state, and which version is active. |
60
+ | `openRouterPromptVersion.create` | Publish a version. Allocates its number and locks the one it succeeds. |
61
+ | `openRouterPromptVersion.update` | Edit the head version in place. Refuses a locked one. |
62
+
63
+ A prompt has no `create`: it comes into existence server-side from a seed against an
64
+ `OpenRouterPromptDefinition` the app already ships. Seeding is not CRUD either — exposing it would hand
65
+ any admin a button that rewrites prompt pointers.
66
+
67
+ **Prefer `read` over `model-get`.** Both models are registered model services, so either document is
68
+ fetchable by key, but the prompt document holds only pointers (`av`, `lv`) and none of what the prompt
69
+ says. Following them by hand means addressing the version subcollection at a zero-padded id, and still
70
+ misses the definition fallback. `read` resolves, and reports `source: 'store' | 'definition'` so the
71
+ precedence rule is observable rather than rederived:
72
+
73
+ | Store state | Served | `source` |
74
+ |---|---|---|
75
+ | No prompt document | Code definition | `definition` |
76
+ | Stored `activeVersion` **≥** the definition's declared version | Store | `store` |
77
+ | Stored `activeVersion` **<** the definition's declared version | Code definition | `definition` |
78
+
79
+ ### Access
80
+
81
+ Prompts are operational configuration, not secrets — nothing here is encrypted, and reads are
82
+ **admin-only** rather than server-only. An admin who can edit a prompt has to be able to read back what
83
+ they wrote, which is why neither model carries `@dbxModelServerOnly`: that tag is copied onto the
84
+ generated CLI/MCP manifest, so a consuming app cannot override it, and the CLI would refuse the read
85
+ locally before choosing a transport.
86
+
87
+ A consuming app that wants these closed does so in the two places that actually refuse a read, and must
88
+ do **both** — they mirror each other, and `dbx_model_server_only_validate_app` reports the drift when
89
+ they disagree:
90
+
91
+ 1. omit the `orp` / `orpv` match blocks from `firestore.rules`, and
92
+ 2. set `serverOnly: true` on the model's `firebaseModelServiceFactory`.
93
+
94
+ An app that leaves them open grants the read to system admins:
95
+
96
+ ```
97
+ match /orp/{openRouterPrompt} {
98
+ allow read: if userClaimsIsSysAdmin();
99
+
100
+ match /orpv/{openRouterPromptVersion} {
101
+ allow read: if userClaimsIsSysAdmin();
102
+ }
103
+ }
104
+
105
+ // the model API addresses a version through the collection GROUP, which the nested match does not cover
106
+ match /{path=**}/orpv/{openRouterPromptVersion} {
107
+ allow read: if userClaimsIsSysAdmin();
108
+ }
109
+ ```
110
+
111
+ `orrt` (`OpenRouterRunTask`) is readable by a system admin too — it is the execution record an operator
112
+ reaches for when a run fails, and its `msg` field carrying the model's raw input and output is the
113
+ reason to read it. It is granted `read` ALONE, on both transports: the sweep owns every write (claim,
114
+ lease, state transition), so a client write would move a task out from under the sweep executing it.
115
+
116
+ ```
117
+ match /orrt/{openRouterRunTask} {
118
+ allow read: if userClaimsIsSysAdmin();
119
+ }
120
+ ```
121
+
49
122
  ## Files and PDFs
50
123
 
51
124
  There is no upload step. A run task stores the **GCS object path** (`fp`) and nothing else — never a
@@ -1,5 +1,5 @@
1
1
  import { MS_IN_DAY } from '@dereekb/util';
2
- import { firestoreModelIdentity, snapshotConverterFunctions, optionalFirestoreArray, firestoreNumber, optionalFirestoreNumber, firestoreEnum, optionalFirestoreString, firestoreString, optionalFirestoreDate, firestoreDate, optionalFirestoreBoolean, optionalFirestorePassthroughJsonField, firestoreArray, AbstractFirestoreDocument, AbstractFirestoreDocumentWithParent, inferredTargetModelParamsType, callModelFirebaseFunctionMapFactory, where, whereDateIsOnOrBefore, orderBy, limit } from '@dereekb/firebase';
2
+ import { firestoreModelIdentity, snapshotConverterFunctions, optionalFirestoreArray, firestoreNumber, optionalFirestoreNumber, firestoreEnum, optionalFirestoreString, firestoreString, optionalFirestoreDate, firestoreDate, optionalFirestoreBoolean, optionalFirestoreJsonStringField, firestoreArray, AbstractFirestoreDocument, AbstractFirestoreDocumentWithParent, inferredTargetModelParamsType, callModelFirebaseFunctionMapFactory, where, whereDateIsOnOrBefore, orderBy, limit } from '@dereekb/firebase';
3
3
  import { type } from 'arktype';
4
4
  import { clearable } from '@dereekb/model';
5
5
 
@@ -213,7 +213,7 @@ var openRouterPromptVersionConverter = snapshotConverterFunctions({
213
213
  m: optionalFirestoreArray({
214
214
  dontStoreIfEmpty: true
215
215
  }),
216
- c: optionalFirestorePassthroughJsonField(),
216
+ c: optionalFirestoreJsonStringField(),
217
217
  nt: optionalFirestoreString(),
218
218
  by: optionalFirestoreString(),
219
219
  lk: optionalFirestoreBoolean()
@@ -390,15 +390,15 @@ var openRouterRunTaskConverter = snapshotConverterFunctions({
390
390
  fa: optionalFirestoreArray({
391
391
  dontStoreIfEmpty: true
392
392
  }),
393
- co: optionalFirestorePassthroughJsonField(),
393
+ co: optionalFirestoreJsonStringField(),
394
394
  o: optionalFirestoreString(),
395
- j: optionalFirestorePassthroughJsonField(),
395
+ j: optionalFirestoreJsonStringField(),
396
396
  gi: optionalFirestoreArray({
397
397
  filterUnique: true,
398
398
  dontStoreIfEmpty: true
399
399
  }),
400
- u: optionalFirestorePassthroughJsonField(),
401
- e: optionalFirestorePassthroughJsonField(),
400
+ u: optionalFirestoreJsonStringField(),
401
+ e: optionalFirestoreJsonStringField(),
402
402
  msg: optionalFirestoreArray({
403
403
  dontStoreIfEmpty: true
404
404
  }),
@@ -494,12 +494,16 @@ var updateOpenRouterPromptVersionParamsType = /* @__PURE__ */ inferredTargetMode
494
494
  'config?': clearable('object'),
495
495
  'notes?': clearable('string')
496
496
  }));
497
+ var readOpenRouterPromptParamsType = /* @__PURE__ */ inferredTargetModelParamsType.merge(type({
498
+ 'version?': 'number'
499
+ }));
497
500
  var readOpenRouterRunTaskParamsType = /* @__PURE__ */ type({
498
501
  key: 'string >= 1'
499
502
  });
500
503
  var OPENROUTER_PROMPT_FUNCTION_TYPE_CONFIG_MAP = {};
501
504
  var OPENROUTER_PROMPT_MODEL_CRUD_FUNCTIONS_CONFIG = {
502
505
  openRouterPrompt: [
506
+ 'read',
503
507
  'update',
504
508
  'query'
505
509
  ],
@@ -615,4 +619,4 @@ var OPENROUTER_PROMPT_MODEL_CRUD_FUNCTIONS_CONFIG = {
615
619
  ];
616
620
  }
617
621
 
618
- export { OPENROUTER_PROMPT_FUNCTION_TYPE_CONFIG_MAP, OPENROUTER_PROMPT_MODEL_CRUD_FUNCTIONS_CONFIG, OPENROUTER_PROMPT_VERSION_ID_DIGITS, OPENROUTER_RUN_TASK_CLAIMABLE_STATES, OPENROUTER_RUN_TASK_MAX_AGE, OPENROUTER_RUN_TASK_TERMINAL_STATES, OpenRouterPromptDocument, OpenRouterPromptModelFunctions, OpenRouterPromptState, OpenRouterPromptVersionDocument, OpenRouterRunTaskDocument, OpenRouterRunTaskState, createOpenRouterPromptVersionParamsType, isOpenRouterRunTaskStateTerminal, openRouterPromptCollectionReference, openRouterPromptConverter, openRouterPromptFirestoreCollection, openRouterPromptIdentity, openRouterPromptModelFunctionMap, openRouterPromptVersionCollectionReference, openRouterPromptVersionCollectionReferenceFactory, openRouterPromptVersionConverter, openRouterPromptVersionDocumentId, openRouterPromptVersionFirestoreCollectionFactory, openRouterPromptVersionFirestoreCollectionGroup, openRouterPromptVersionId, openRouterPromptVersionIdentity, openRouterPromptVersionMessageParamsType, openRouterPromptVersionNumberFromId, openRouterPromptsWithStateQuery, openRouterResolvedPromptForVersion, openRouterRunTaskCollectionReference, openRouterRunTaskConverter, openRouterRunTaskFirestoreCollection, openRouterRunTaskIdentity, openRouterRunTasksExpiredQuery, openRouterRunTasksReclaimableQuery, openRouterRunTasksRunnableQuery, readOpenRouterRunTaskParamsType, updateOpenRouterPromptParamsType, updateOpenRouterPromptVersionParamsType };
622
+ export { OPENROUTER_PROMPT_FUNCTION_TYPE_CONFIG_MAP, OPENROUTER_PROMPT_MODEL_CRUD_FUNCTIONS_CONFIG, OPENROUTER_PROMPT_VERSION_ID_DIGITS, OPENROUTER_RUN_TASK_CLAIMABLE_STATES, OPENROUTER_RUN_TASK_MAX_AGE, OPENROUTER_RUN_TASK_TERMINAL_STATES, OpenRouterPromptDocument, OpenRouterPromptModelFunctions, OpenRouterPromptState, OpenRouterPromptVersionDocument, OpenRouterRunTaskDocument, OpenRouterRunTaskState, createOpenRouterPromptVersionParamsType, isOpenRouterRunTaskStateTerminal, openRouterPromptCollectionReference, openRouterPromptConverter, openRouterPromptFirestoreCollection, openRouterPromptIdentity, openRouterPromptModelFunctionMap, openRouterPromptVersionCollectionReference, openRouterPromptVersionCollectionReferenceFactory, openRouterPromptVersionConverter, openRouterPromptVersionDocumentId, openRouterPromptVersionFirestoreCollectionFactory, openRouterPromptVersionFirestoreCollectionGroup, openRouterPromptVersionId, openRouterPromptVersionIdentity, openRouterPromptVersionMessageParamsType, openRouterPromptVersionNumberFromId, openRouterPromptsWithStateQuery, openRouterResolvedPromptForVersion, openRouterRunTaskCollectionReference, openRouterRunTaskConverter, openRouterRunTaskFirestoreCollection, openRouterRunTaskIdentity, openRouterRunTasksExpiredQuery, openRouterRunTasksReclaimableQuery, openRouterRunTasksRunnableQuery, readOpenRouterPromptParamsType, readOpenRouterRunTaskParamsType, updateOpenRouterPromptParamsType, updateOpenRouterPromptVersionParamsType };
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@dereekb/openrouter/firebase",
3
- "version": "14.1.0",
3
+ "version": "14.3.0",
4
+ "sideEffects": false,
4
5
  "type": "module",
5
6
  "peerDependencies": {
6
- "@dereekb/date": "14.1.0",
7
- "@dereekb/firebase": "14.1.0",
8
- "@dereekb/model": "14.1.0",
9
- "@dereekb/openrouter": "14.1.0",
10
- "@dereekb/rxjs": "14.1.0",
11
- "@dereekb/util": "14.1.0",
7
+ "@dereekb/date": "14.3.0",
8
+ "@dereekb/firebase": "14.3.0",
9
+ "@dereekb/model": "14.3.0",
10
+ "@dereekb/openrouter": "14.3.0",
11
+ "@dereekb/rxjs": "14.3.0",
12
+ "@dereekb/util": "14.3.0",
12
13
  "arktype": "^2.2.0"
13
14
  },
14
15
  "exports": {
@@ -1,7 +1,7 @@
1
1
  import { type Type } from 'arktype';
2
2
  import { type FirebaseFunctionTypeConfigMap, type FirestoreModelKey, type InferredTargetModelParams, type ModelFirebaseCreateFunction, type ModelFirebaseCrudFunction, type ModelFirebaseCrudFunctionConfigMap, type ModelFirebaseFunctionMap, type ModelFirebaseQueryFunction, type OnCallCreateModelResult, type OnCallQueryModelRequestParams, type OnCallQueryModelResult } from '@dereekb/firebase';
3
3
  import { type Maybe } from '@dereekb/util';
4
- import { type OpenRouterPromptVersionNumber } from '@dereekb/openrouter';
4
+ import { type OpenRouterPromptResolutionSource, type OpenRouterPromptVersionNumber, type OpenRouterResolvedPrompt } from '@dereekb/openrouter';
5
5
  import { type OpenRouterPrompt, type OpenRouterPromptState, type OpenRouterPromptTypes } from './openrouter.model';
6
6
  /**
7
7
  * Parameters for updating an {@link OpenRouterPrompt}'s metadata and lifecycle state.
@@ -160,6 +160,64 @@ export interface QueryOpenRouterPromptsParams extends OnCallQueryModelRequestPar
160
160
  */
161
161
  readonly state?: Maybe<OpenRouterPromptState>;
162
162
  }
163
+ /**
164
+ * Parameters for reading an {@link OpenRouterPrompt} together with the version it serves.
165
+ *
166
+ * @dbxModelApiParams
167
+ */
168
+ export interface ReadOpenRouterPromptParams extends InferredTargetModelParams {
169
+ /**
170
+ * The version to read. Omit to read the version an unpinned caller is served right now.
171
+ */
172
+ readonly version?: Maybe<OpenRouterPromptVersionNumber>;
173
+ }
174
+ export declare const readOpenRouterPromptParamsType: Type<ReadOpenRouterPromptParams>;
175
+ /**
176
+ * Result of reading a prompt.
177
+ *
178
+ * Returns the RESOLVED version rather than a stored document, because the stored document is not
179
+ * necessarily what the app serves: a code {@link OpenRouterPromptDefinition} stands in when the store
180
+ * cannot serve, or is behind it. Reading the two documents by key would answer "what is stored", which
181
+ * is a different — and, when they disagree, misleading — question from "what will run".
182
+ */
183
+ export interface ReadOpenRouterPromptResult {
184
+ /**
185
+ * The stored prompt document, or null when the prompt exists only as a code definition.
186
+ *
187
+ * Null here alongside a populated {@link resolved} is the never-seeded case, not an error.
188
+ */
189
+ readonly prompt: Maybe<OpenRouterPrompt>;
190
+ /**
191
+ * The version that will actually be served — instructions, seed messages, and model config.
192
+ *
193
+ * This is the read the version model could not previously give back: a version could be written and
194
+ * never read, so an author edited a prompt blind.
195
+ */
196
+ readonly resolved: OpenRouterResolvedPrompt;
197
+ /**
198
+ * Which half of the resolution won: the stored version, or the code definition standing in for it.
199
+ *
200
+ * The one field that makes the precedence rule observable. A caller who published version 3 and is
201
+ * still being served version 4 from code sees `definition` here rather than having to rederive why.
202
+ */
203
+ readonly source: OpenRouterPromptResolutionSource;
204
+ /**
205
+ * Config problems that do not stop the prompt from being served.
206
+ *
207
+ * Same warnings a create or update returns, reported here so a prompt that was published before a
208
+ * validation rule existed still surfaces them on read.
209
+ */
210
+ readonly warnings: string[];
211
+ /**
212
+ * Config problems that make {@link resolved} unusable.
213
+ *
214
+ * Separate from {@link warnings}, and non-empty only in cases a create would have refused. A read
215
+ * still returns rather than throwing on them: the resolver rejects an invalid config only when it is
216
+ * configured with `rejectInvalidConfig`, so such a version CAN be the live one — and showing the
217
+ * caller the config that is breaking their calls is the entire point of the read.
218
+ */
219
+ readonly errors: string[];
220
+ }
163
221
  /**
164
222
  * Parameters for reading an {@link OpenRouterRunTask}.
165
223
  *
@@ -190,14 +248,21 @@ export declare const OPENROUTER_PROMPT_FUNCTION_TYPE_CONFIG_MAP: FirebaseFunctio
190
248
  * appears — rather than as a specifier on the parent's update. Its `update` edits the latest version
191
249
  * in place and refuses a locked one, so iterating on a prompt does not mint a version per keystroke.
192
250
  *
193
- * Neither model declares a `read`: both are registered model services, so a stored prompt or one of its
194
- * versions is already fetchable by key through model-get.
251
+ * The prompt declares a `read` on top of model-get, because model-get answers a different question: it
252
+ * returns the stored DOCUMENT, which holds only pointers (`av`, `lv`) and none of what the prompt says.
253
+ * Following it by hand means reading the version subcollection at a zero-padded id the caller has to
254
+ * construct, and still leaves the code-definition fallback invisible. The `read` returns what will
255
+ * actually be served, and says which half of the resolution produced it.
256
+ *
257
+ * The VERSION declares no `read`: it is a registered model service, so a specific stored version is
258
+ * already fetchable by key through model-get, and the prompt's `read` covers the "what is live" case.
195
259
  *
196
260
  * `OpenRouterRunTask` is absent on purpose. A run task is written and drained entirely server-side, and
197
261
  * its `msg` field carries raw model input and output.
198
262
  */
199
263
  export type OpenRouterPromptModelCrudFunctionsConfig = {
200
264
  readonly openRouterPrompt: {
265
+ read: [ReadOpenRouterPromptParams, ReadOpenRouterPromptResult];
201
266
  update: UpdateOpenRouterPromptParams;
202
267
  query: [QueryOpenRouterPromptsParams, OnCallQueryModelResult<OpenRouterPrompt>];
203
268
  };
@@ -212,6 +277,7 @@ export declare const OPENROUTER_PROMPT_MODEL_CRUD_FUNCTIONS_CONFIG: ModelFirebas
212
277
  */
213
278
  export declare abstract class OpenRouterPromptModelFunctions implements ModelFirebaseFunctionMap<OpenRouterPromptFunctionTypeMap, OpenRouterPromptModelCrudFunctionsConfig> {
214
279
  abstract openRouterPrompt: {
280
+ readOpenRouterPrompt: ModelFirebaseCrudFunction<ReadOpenRouterPromptParams, ReadOpenRouterPromptResult>;
215
281
  updateOpenRouterPrompt: ModelFirebaseCrudFunction<UpdateOpenRouterPromptParams>;
216
282
  queryOpenRouterPrompt: ModelFirebaseQueryFunction<QueryOpenRouterPromptsParams, OnCallQueryModelResult<OpenRouterPrompt>>;
217
283
  };
@@ -49,10 +49,20 @@ export declare enum OpenRouterPromptState {
49
49
  * The prompt document holds only identity and version pointers; everything servable lives on an
50
50
  * {@link OpenRouterPromptVersion}.
51
51
  *
52
+ * NOT `@dbxModelServerOnly`, deliberately. The tag means "no client may read this on any path", which
53
+ * a downstream app cannot override: it is copied onto the generated CLI/MCP manifest, so the CLI
54
+ * refuses the read locally before it picks a transport. That is right for the models that carry it —
55
+ * system plumbing tagged `@dbxModelRead system` — but a prompt is `@dbxModelRead admin-only` operational
56
+ * configuration, and an admin reading the configuration they are allowed to edit is the normal case.
57
+ * Nothing here is encrypted; `serverOnly` is an access posture, not confidentiality at rest.
58
+ *
59
+ * Server-only-ness is a PER-APP property anyway, because it follows from that app's `firestore.rules`.
60
+ * An app that wants these closed still closes them, in the two places that actually refuse a read:
61
+ * omit the `orp` match block, and set `serverOnly: true` on its `firebaseModelServiceFactory`.
62
+ *
52
63
  * @dbxModel
53
- * @dbxModelRead admin
64
+ * @dbxModelRead admin-only
54
65
  * @dbxModelUpdate admin
55
- * @dbxModelServerOnly
56
66
  */
57
67
  export interface OpenRouterPrompt {
58
68
  /**
@@ -169,9 +179,13 @@ export interface OpenRouterPromptVersionMessage {
169
179
  * iterated on without minting a version per keystroke — which does mean a run against the head can be
170
180
  * replayed against text that has since moved. Lock it by creating the next version.
171
181
  *
182
+ * NOT `@dbxModelServerOnly`, for the reason given on {@link OpenRouterPrompt} — and more so here, since
183
+ * this is the model that holds what a prompt actually SAYS. Tagging it meant an author could write a
184
+ * version and never read back what they wrote.
185
+ *
172
186
  * @dbxModel
173
- * @dbxModelRead admin
174
- * @dbxModelServerOnly
187
+ * @dbxModelRead admin-only
188
+ * @dbxModelUpdate admin
175
189
  */
176
190
  export interface OpenRouterPromptVersion {
177
191
  /**
@@ -201,10 +215,15 @@ export interface OpenRouterPromptVersion {
201
215
  /**
202
216
  * Model configuration.
203
217
  *
204
- * Stored as PASSTHROUGH JSON, deliberately not a strict converter. OpenRouter's parameter surface
205
- * moves fast, and a strict converter would silently drop any field it did not know about — turning
206
- * every OpenRouter release into a config-corrupting event. `OpenRouterModelConfig` types it in
207
- * TypeScript for autocomplete and call-time validation instead: strict types in code, loose storage.
218
+ * Stored as a JSON STRING, deliberately not a strict converter. OpenRouter's parameter surface moves
219
+ * fast, and a strict converter would silently drop any field it did not know about — turning every
220
+ * OpenRouter release into a config-corrupting event. `OpenRouterModelConfig` types it in TypeScript
221
+ * for autocomplete and call-time validation instead: strict types in code, loose storage.
222
+ *
223
+ * A string rather than a native Firestore map because this config carries a json schema, and a map
224
+ * cannot hold every legal one: Firestore forbids an array inside an array, so an array-valued `enum`,
225
+ * `const`, `default`, or `examples` fails the write outright rather than degrading. Serializing costs
226
+ * queryability on the config's interior, which nothing wants, and buys back the whole json type system.
208
227
  *
209
228
  * @dbxModelVariable config
210
229
  */
@@ -415,7 +434,7 @@ export interface OpenRouterRunTaskUnsentToolResult {
415
434
  * run reuses this document instead of queueing a duplicate.
416
435
  *
417
436
  * @dbxModel
418
- * @dbxModelRead admin
437
+ * @dbxModelRead admin-only
419
438
  * @dbxModelUpdate admin
420
439
  */
421
440
  export interface OpenRouterRunTask {
@@ -1,8 +1,16 @@
1
1
  import { firestoreModelId, firestoreModelKeyParentKey, getDocumentSnapshotDataTuples } from '@dereekb/firebase';
2
2
  import { runAsyncTasksForValues, mergeObjects, KeyValueTypleValueFilter, concatArraysUnique, MS_IN_MINUTE, arrayToMap, expiringCachedGetter, filterMaybeArrayValues, filterUniqueValues, mergeArrays, addMilliseconds, filterUndefinedValues, randomNumberFactory, performTasksInParallel } from '@dereekb/util';
3
3
  import { validateOpenRouterModelConfig, openRouterPromptRequest, callModelForOpenRouterRequest, openRouterMessagesWithoutFileAttachmentData, openRouterMessagesWithFreshFileAttachments, openRouterInputMessages, openRouterFunctionCallOutputItems } from '@dereekb/openrouter';
4
- import { updateOpenRouterPromptParamsType, createOpenRouterPromptVersionParamsType, updateOpenRouterPromptVersionParamsType, OpenRouterPromptState, openRouterPromptVersionId, openRouterResolvedPromptForVersion, OpenRouterRunTaskState, openRouterRunTasksRunnableQuery, openRouterRunTasksReclaimableQuery, OPENROUTER_RUN_TASK_MAX_AGE, openRouterRunTasksExpiredQuery, isOpenRouterRunTaskStateTerminal } from '@dereekb/openrouter/firebase';
4
+ import { updateOpenRouterPromptParamsType, readOpenRouterPromptParamsType, createOpenRouterPromptVersionParamsType, updateOpenRouterPromptVersionParamsType, OpenRouterPromptState, openRouterPromptVersionId, openRouterResolvedPromptForVersion, OpenRouterRunTaskState, openRouterRunTasksRunnableQuery, openRouterRunTasksReclaimableQuery, OPENROUTER_RUN_TASK_MAX_AGE, openRouterRunTasksExpiredQuery, isOpenRouterRunTaskStateTerminal } from '@dereekb/openrouter/firebase';
5
5
 
6
+ function _array_like_to_array$2(arr, len) {
7
+ if (len == null || len > arr.length) len = arr.length;
8
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
9
+ return arr2;
10
+ }
11
+ function _array_with_holes$1(arr) {
12
+ if (Array.isArray(arr)) return arr;
13
+ }
6
14
  function asyncGeneratorStep$8(gen, resolve, reject, _next, _throw, key, arg) {
7
15
  try {
8
16
  var info = gen[key](arg);
@@ -43,6 +51,33 @@ function _define_property$3(obj, key, value) {
43
51
  } else obj[key] = value;
44
52
  return obj;
45
53
  }
54
+ function _iterable_to_array_limit$1(arr, i) {
55
+ var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
56
+ if (_i == null) return;
57
+ var _arr = [];
58
+ var _n = true;
59
+ var _d = false;
60
+ var _s, _e;
61
+ try {
62
+ for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
63
+ _arr.push(_s.value);
64
+ if (i && _arr.length === i) break;
65
+ }
66
+ } catch (err) {
67
+ _d = true;
68
+ _e = err;
69
+ } finally{
70
+ try {
71
+ if (!_n && _i["return"] != null) _i["return"]();
72
+ } finally{
73
+ if (_d) throw _e;
74
+ }
75
+ }
76
+ return _arr;
77
+ }
78
+ function _non_iterable_rest$1() {
79
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
80
+ }
46
81
  function _object_spread$2(target) {
47
82
  for(var i = 1; i < arguments.length; i++){
48
83
  var source = arguments[i] != null ? arguments[i] : {};
@@ -58,6 +93,9 @@ function _object_spread$2(target) {
58
93
  }
59
94
  return target;
60
95
  }
96
+ function _sliced_to_array$1(arr, i) {
97
+ return _array_with_holes$1(arr) || _iterable_to_array_limit$1(arr, i) || _unsupported_iterable_to_array$2(arr, i) || _non_iterable_rest$1();
98
+ }
61
99
  function _ts_generator$8(thisArg, body) {
62
100
  var f, y, t, _ = {
63
101
  label: 0,
@@ -157,13 +195,24 @@ function _ts_generator$8(thisArg, body) {
157
195
  };
158
196
  }
159
197
  }
198
+ function _unsupported_iterable_to_array$2(o, minLen) {
199
+ if (!o) return;
200
+ if (typeof o === "string") return _array_like_to_array$2(o, minLen);
201
+ var n = Object.prototype.toString.call(o).slice(8, -1);
202
+ if (n === "Object" && o.constructor) n = o.constructor.name;
203
+ if (n === "Map" || n === "Set") return Array.from(n);
204
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$2(o, minLen);
205
+ }
160
206
  /**
161
207
  * Server actions for managing prompts.
162
208
  *
163
- * The writes exist instead of an Angular UI: they reach the model API, and the existing callModel MCP
164
- * surface makes them callable without building a screen for it. Reads are absent because the prompt and
165
- * version model services already make both fetchable by key through model-get, and listing is the
166
- * model API's standard query operation over {@link openRouterPromptsWithStateQuery}.
209
+ * These exist instead of an Angular UI: they reach the model API, and the existing callModel MCP
210
+ * surface makes them callable without building a screen for it.
211
+ *
212
+ * The one read is {@link readOpenRouterPrompt}, which answers "what does this prompt serve right now".
213
+ * Fetching either document by key is already covered by model-get, and listing by the model API's
214
+ * standard query over {@link openRouterPromptsWithStateQuery}, so the read earns its place only by
215
+ * resolving — following the version pointer and the code-definition fallback that model-get cannot.
167
216
  */ var OpenRouterPromptServerActions = function OpenRouterPromptServerActions() {
168
217
  _class_call_check$3(this, OpenRouterPromptServerActions);
169
218
  };
@@ -174,6 +223,7 @@ function _ts_generator$8(thisArg, body) {
174
223
  * @returns The server actions.
175
224
  */ function openRouterPromptServerActions(context) {
176
225
  return {
226
+ readOpenRouterPrompt: readOpenRouterPromptFactory(context),
177
227
  createOpenRouterPrompt: createOpenRouterPromptFactory(context),
178
228
  updateOpenRouterPrompt: updateOpenRouterPromptFactory(context),
179
229
  createOpenRouterPromptVersion: createOpenRouterPromptVersionFactory(context),
@@ -332,6 +382,71 @@ function _ts_generator$8(thisArg, body) {
332
382
  })();
333
383
  });
334
384
  }
385
+ /**
386
+ * Reads a prompt together with the version it actually serves.
387
+ *
388
+ * Goes through the prompt SERVICE rather than reading the two documents directly, so the answer is the
389
+ * one a dispatch would get — code definition included. Reading the documents here would reimplement the
390
+ * precedence rule in a second place, where it could drift from the resolver it is meant to describe.
391
+ *
392
+ * @param context - The actions context.
393
+ * @returns The read action.
394
+ */ function readOpenRouterPromptFactory(context) {
395
+ var firebaseServerActionTransformFunctionFactory = context.firebaseServerActionTransformFunctionFactory, openRouterPromptService = context.openRouterPromptService;
396
+ return firebaseServerActionTransformFunctionFactory(readOpenRouterPromptParamsType, function(params) {
397
+ return _async_to_generator$8(function() {
398
+ var version;
399
+ return _ts_generator$8(this, function(_state) {
400
+ version = params.version;
401
+ return [
402
+ 2,
403
+ function(document) {
404
+ return _async_to_generator$8(function() {
405
+ var promptKey, _ref, prompt, resolution, resolved, source, validation, result;
406
+ return _ts_generator$8(this, function(_state) {
407
+ switch(_state.label){
408
+ case 0:
409
+ promptKey = document.id;
410
+ return [
411
+ 4,
412
+ Promise.all([
413
+ openRouterPromptService.loadPrompt(promptKey),
414
+ openRouterPromptService.readPrompt({
415
+ promptKey: promptKey,
416
+ version: version
417
+ })
418
+ ])
419
+ ];
420
+ case 1:
421
+ _ref = _sliced_to_array$1.apply(void 0, [
422
+ _state.sent(),
423
+ 2
424
+ ]), prompt = _ref[0], resolution = _ref[1];
425
+ resolved = resolution.resolved, source = resolution.source;
426
+ validation = validateOpenRouterModelConfig(resolved.config);
427
+ result = {
428
+ prompt: prompt !== null && prompt !== void 0 ? prompt : null,
429
+ resolved: resolved,
430
+ source: source,
431
+ // Both severities are reported rather than thrown. A create refuses an invalid config, but the
432
+ // resolver only refuses one when configured to, so an unusable config can already be live —
433
+ // and showing it is what the read is for.
434
+ warnings: validation.warnings,
435
+ errors: validation.errors
436
+ };
437
+ return [
438
+ 2,
439
+ result
440
+ ];
441
+ }
442
+ });
443
+ })();
444
+ }
445
+ ];
446
+ });
447
+ })();
448
+ });
449
+ }
335
450
  /**
336
451
  * Creates a new version, allocating its number inside the transaction, locking the version it succeeds,
337
452
  * and optionally promoting it.
@@ -1994,7 +2109,7 @@ function _wrap_native_super(Class) {
1994
2109
  }
1995
2110
  function resolveVersion(promptKey, inputVersion) {
1996
2111
  return _async_to_generator$4(function() {
1997
- var definition, promptDocument, prompt, storedActiveVersion, resolved, _tmp, validation;
2112
+ var definition, promptDocument, prompt, storedActiveVersion, resolved, source, _tmp, validation;
1998
2113
  return _ts_generator$4(this, function(_state) {
1999
2114
  switch(_state.label){
2000
2115
  case 0:
@@ -2011,6 +2126,10 @@ function _wrap_native_super(Class) {
2011
2126
  // A pinned caller is still allowed to read a draft/archived prompt: that is how a version is tested
2012
2127
  // before promotion, and how a historical run is replayed after retirement.
2013
2128
  storedActiveVersion = (prompt === null || prompt === void 0 ? void 0 : prompt.s) === OpenRouterPromptState.ACTIVE ? prompt.av : undefined;
2129
+ // Tracked alongside `resolved` rather than inferred from it afterwards: a definition and a stored
2130
+ // version that agree are indistinguishable by value, and the branch that picked one is the only
2131
+ // place that actually knows.
2132
+ source = 'store';
2014
2133
  if (!(inputVersion != null)) return [
2015
2134
  3,
2016
2135
  5
@@ -2039,6 +2158,7 @@ function _wrap_native_super(Class) {
2039
2158
  resolved = _tmp;
2040
2159
  if (resolved == null && (definition === null || definition === void 0 ? void 0 : definition.version) === inputVersion) {
2041
2160
  resolved = definition;
2161
+ source = 'definition';
2042
2162
  }
2043
2163
  return [
2044
2164
  3,
@@ -2051,6 +2171,7 @@ function _wrap_native_super(Class) {
2051
2171
  ];
2052
2172
  // Unpinned, and code is either standing in for the store or ahead of it.
2053
2173
  resolved = definition;
2174
+ source = 'definition';
2054
2175
  return [
2055
2176
  3,
2056
2177
  8
@@ -2089,7 +2210,10 @@ function _wrap_native_super(Class) {
2089
2210
  }
2090
2211
  return [
2091
2212
  2,
2092
- resolved
2213
+ {
2214
+ resolved: resolved,
2215
+ source: source
2216
+ }
2093
2217
  ];
2094
2218
  }
2095
2219
  });
@@ -2098,7 +2222,7 @@ function _wrap_native_super(Class) {
2098
2222
  function cacheKey(promptKey, version) {
2099
2223
  return "".concat(promptKey, ":").concat(version !== null && version !== void 0 ? version : '_');
2100
2224
  }
2101
- function resolvePrompt(params) {
2225
+ function readPrompt(params) {
2102
2226
  return _async_to_generator$4(function() {
2103
2227
  var promptKey, version, key, getter, load, result, e;
2104
2228
  return _ts_generator$4(this, function(_state) {
@@ -2155,6 +2279,26 @@ function _wrap_native_super(Class) {
2155
2279
  });
2156
2280
  })();
2157
2281
  }
2282
+ function resolvePrompt(params) {
2283
+ return _async_to_generator$4(function() {
2284
+ return _ts_generator$4(this, function(_state) {
2285
+ switch(_state.label){
2286
+ case 0:
2287
+ return [
2288
+ 4,
2289
+ readPrompt(params)
2290
+ ];
2291
+ case 1:
2292
+ // Projection of readPrompt rather than a second resolution path, so both share the one cache entry
2293
+ // and cannot disagree about what is being served.
2294
+ return [
2295
+ 2,
2296
+ _state.sent().resolved
2297
+ ];
2298
+ }
2299
+ });
2300
+ })();
2301
+ }
2158
2302
  function clearCachedPrompt(promptKey) {
2159
2303
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2160
2304
  try {
@@ -2186,6 +2330,7 @@ function _wrap_native_super(Class) {
2186
2330
  loadPromptDefinitions: loadPromptDefinitions,
2187
2331
  loadPrompt: loadPrompt,
2188
2332
  resolvePrompt: resolvePrompt,
2333
+ readPrompt: readPrompt,
2189
2334
  clearCachedPrompt: clearCachedPrompt
2190
2335
  };
2191
2336
  }
@@ -4413,4 +4558,4 @@ var randomSweepId = randomNumberFactory({
4413
4558
  })();
4414
4559
  }
4415
4560
 
4416
- export { AbstractAppOpenRouterModule, DEFAULT_OPENROUTER_EXPIRATION_SWEEP_MAX_RUN_TIME, DEFAULT_OPENROUTER_EXPIRATION_SWEEP_PAGE_SIZE, DEFAULT_OPENROUTER_INLINE_FILE_CONTENT_TYPE, DEFAULT_OPENROUTER_LEASE_DURATION, DEFAULT_OPENROUTER_MAX_ATTEMPTS, DEFAULT_OPENROUTER_MAX_INLINE_FILE_SIZE_BYTES, DEFAULT_OPENROUTER_SIGNED_URL_TTL, DEFAULT_OPENROUTER_SWEEP_MAX_PARALLEL_TASKS, DEFAULT_OPENROUTER_SWEEP_MAX_RUN_TIME, DEFAULT_OPENROUTER_SWEEP_PAGE_SIZE, OPENROUTER_BROADCAST_RUN_TASK_KEY_ATTRIBUTES, OPENROUTER_MAX_EXPIRED_RUN_TASK_DELETE_PAGE_SIZE, OPENROUTER_PERMANENT_ERROR_STATUSES, OPENROUTER_PROMPT_CACHE_DURATION, OPENROUTER_PROMPT_SERVICE_TOKEN, OPENROUTER_RUN_TASK_SERVICE_TOKEN, OPENROUTER_SEED_PROMPTS_MAX_PARALLEL_UPDATES, OpenRouterPromptResolutionError, OpenRouterPromptServerActions, OpenRouterPromptService, OpenRouterRunTaskService, appOpenRouterModuleMetadata, callModelForPrompt, conversationStateForOpenRouterRunTask, conversationStatusForOpenRouterRunTaskState, createOpenRouterPromptFactory, createOpenRouterPromptVersionFactory, firestoreOpenRouterStateAccessor, handleOpenRouterRunTaskResultFactory, hasUnresolvedOpenRouterPendingToolCalls, isOpenRouterRunTaskClaimable, isRetryableOpenRouterError, mergeOpenRouterRunUsage, openRouterConversationValueForFirestore, openRouterDeferredToolResolutionsForRunTask, openRouterErrorCode, openRouterErrorMessage, openRouterFileAttachmentModeForConfig, openRouterFileAttachmentResolver, openRouterPromptServerActions, openRouterPromptService, openRouterRunTaskExpirationSweep, openRouterRunTaskKeyFromBroadcastAttributes, openRouterRunTaskOutcome, openRouterRunTaskService, openRouterRunTaskStateForConversationStatus, openRouterRunTaskSweep, openRouterRunTaskUpdateForConversationState, reconcileOpenRouterRunTaskFromBroadcast, seedOpenRouterPromptsFactory, updateOpenRouterPromptFactory, updateOpenRouterPromptVersionFactory };
4561
+ export { AbstractAppOpenRouterModule, DEFAULT_OPENROUTER_EXPIRATION_SWEEP_MAX_RUN_TIME, DEFAULT_OPENROUTER_EXPIRATION_SWEEP_PAGE_SIZE, DEFAULT_OPENROUTER_INLINE_FILE_CONTENT_TYPE, DEFAULT_OPENROUTER_LEASE_DURATION, DEFAULT_OPENROUTER_MAX_ATTEMPTS, DEFAULT_OPENROUTER_MAX_INLINE_FILE_SIZE_BYTES, DEFAULT_OPENROUTER_SIGNED_URL_TTL, DEFAULT_OPENROUTER_SWEEP_MAX_PARALLEL_TASKS, DEFAULT_OPENROUTER_SWEEP_MAX_RUN_TIME, DEFAULT_OPENROUTER_SWEEP_PAGE_SIZE, OPENROUTER_BROADCAST_RUN_TASK_KEY_ATTRIBUTES, OPENROUTER_MAX_EXPIRED_RUN_TASK_DELETE_PAGE_SIZE, OPENROUTER_PERMANENT_ERROR_STATUSES, OPENROUTER_PROMPT_CACHE_DURATION, OPENROUTER_PROMPT_SERVICE_TOKEN, OPENROUTER_RUN_TASK_SERVICE_TOKEN, OPENROUTER_SEED_PROMPTS_MAX_PARALLEL_UPDATES, OpenRouterPromptResolutionError, OpenRouterPromptServerActions, OpenRouterPromptService, OpenRouterRunTaskService, appOpenRouterModuleMetadata, callModelForPrompt, conversationStateForOpenRouterRunTask, conversationStatusForOpenRouterRunTaskState, createOpenRouterPromptFactory, createOpenRouterPromptVersionFactory, firestoreOpenRouterStateAccessor, handleOpenRouterRunTaskResultFactory, hasUnresolvedOpenRouterPendingToolCalls, isOpenRouterRunTaskClaimable, isRetryableOpenRouterError, mergeOpenRouterRunUsage, openRouterConversationValueForFirestore, openRouterDeferredToolResolutionsForRunTask, openRouterErrorCode, openRouterErrorMessage, openRouterFileAttachmentModeForConfig, openRouterFileAttachmentResolver, openRouterPromptServerActions, openRouterPromptService, openRouterRunTaskExpirationSweep, openRouterRunTaskKeyFromBroadcastAttributes, openRouterRunTaskOutcome, openRouterRunTaskService, openRouterRunTaskStateForConversationStatus, openRouterRunTaskSweep, openRouterRunTaskUpdateForConversationState, readOpenRouterPromptFactory, reconcileOpenRouterRunTaskFromBroadcast, seedOpenRouterPromptsFactory, updateOpenRouterPromptFactory, updateOpenRouterPromptVersionFactory };
@@ -1,17 +1,18 @@
1
1
  {
2
2
  "name": "@dereekb/openrouter/firebase-server",
3
- "version": "14.1.0",
3
+ "version": "14.3.0",
4
+ "sideEffects": false,
4
5
  "type": "module",
5
6
  "peerDependencies": {
6
- "@dereekb/analytics": "14.1.0",
7
- "@dereekb/date": "14.1.0",
8
- "@dereekb/firebase": "14.1.0",
9
- "@dereekb/firebase-server": "14.1.0",
10
- "@dereekb/model": "14.1.0",
11
- "@dereekb/nestjs": "14.1.0",
12
- "@dereekb/openrouter": "14.1.0",
13
- "@dereekb/rxjs": "14.1.0",
14
- "@dereekb/util": "14.1.0",
7
+ "@dereekb/analytics": "14.3.0",
8
+ "@dereekb/date": "14.3.0",
9
+ "@dereekb/firebase": "14.3.0",
10
+ "@dereekb/firebase-server": "14.3.0",
11
+ "@dereekb/model": "14.3.0",
12
+ "@dereekb/nestjs": "14.3.0",
13
+ "@dereekb/openrouter": "14.3.0",
14
+ "@dereekb/rxjs": "14.3.0",
15
+ "@dereekb/util": "14.3.0",
15
16
  "@nestjs/common": "^12.0.1",
16
17
  "@nestjs/config": "^12.0.0",
17
18
  "@openrouter/sdk": "^1.2.26",
@@ -2,7 +2,7 @@ import { type FirebaseServerActionsContext } from '@dereekb/firebase-server';
2
2
  import { type FirestoreContextReference } from '@dereekb/firebase';
3
3
  import { type Maybe } from '@dereekb/util';
4
4
  import { type OpenRouterPromptKey } from '@dereekb/openrouter';
5
- import { type CreateOpenRouterPromptVersionParams, type CreateOpenRouterPromptVersionResult, type OpenRouterPromptDocument, type OpenRouterPromptFirestoreCollections, type OpenRouterPromptVersionDocument, type UpdateOpenRouterPromptParams, type UpdateOpenRouterPromptVersionParams, type UpdateOpenRouterPromptVersionResult } from '@dereekb/openrouter/firebase';
5
+ import { type CreateOpenRouterPromptVersionParams, type CreateOpenRouterPromptVersionResult, type OpenRouterPromptDocument, type OpenRouterPromptFirestoreCollections, type OpenRouterPromptVersionDocument, type ReadOpenRouterPromptParams, type ReadOpenRouterPromptResult, type UpdateOpenRouterPromptParams, type UpdateOpenRouterPromptVersionParams, type UpdateOpenRouterPromptVersionResult } from '@dereekb/openrouter/firebase';
6
6
  import { type OpenRouterPromptService } from './openrouter.prompt.service';
7
7
  /**
8
8
  * Context required by the OpenRouter prompt server actions.
@@ -91,12 +91,16 @@ export interface SeedOpenRouterPromptsResult {
91
91
  /**
92
92
  * Server actions for managing prompts.
93
93
  *
94
- * The writes exist instead of an Angular UI: they reach the model API, and the existing callModel MCP
95
- * surface makes them callable without building a screen for it. Reads are absent because the prompt and
96
- * version model services already make both fetchable by key through model-get, and listing is the
97
- * model API's standard query operation over {@link openRouterPromptsWithStateQuery}.
94
+ * These exist instead of an Angular UI: they reach the model API, and the existing callModel MCP
95
+ * surface makes them callable without building a screen for it.
96
+ *
97
+ * The one read is {@link readOpenRouterPrompt}, which answers "what does this prompt serve right now".
98
+ * Fetching either document by key is already covered by model-get, and listing by the model API's
99
+ * standard query over {@link openRouterPromptsWithStateQuery}, so the read earns its place only by
100
+ * resolving — following the version pointer and the code-definition fallback that model-get cannot.
98
101
  */
99
102
  export declare abstract class OpenRouterPromptServerActions {
103
+ abstract readOpenRouterPrompt(params: ReadOpenRouterPromptParams): Promise<(document: OpenRouterPromptDocument) => Promise<ReadOpenRouterPromptResult>>;
100
104
  abstract createOpenRouterPrompt(params: CreateOpenRouterPromptParams): Promise<OpenRouterPromptDocument>;
101
105
  abstract updateOpenRouterPrompt(params: UpdateOpenRouterPromptParams): Promise<(document: OpenRouterPromptDocument) => Promise<OpenRouterPromptDocument>>;
102
106
  abstract createOpenRouterPromptVersion(params: CreateOpenRouterPromptVersionParams): Promise<(document: OpenRouterPromptDocument) => Promise<CreateOpenRouterPromptVersionResult>>;
@@ -131,6 +135,17 @@ export declare function createOpenRouterPromptFactory(context: OpenRouterPromptS
131
135
  * @returns The update action.
132
136
  */
133
137
  export declare function updateOpenRouterPromptFactory(context: OpenRouterPromptServerActionsContext): import("@dereekb/model").TransformAndValidateFunctionResultFunction<UpdateOpenRouterPromptParams, (document: OpenRouterPromptDocument) => Promise<OpenRouterPromptDocument>, object, unknown>;
138
+ /**
139
+ * Reads a prompt together with the version it actually serves.
140
+ *
141
+ * Goes through the prompt SERVICE rather than reading the two documents directly, so the answer is the
142
+ * one a dispatch would get — code definition included. Reading the documents here would reimplement the
143
+ * precedence rule in a second place, where it could drift from the resolver it is meant to describe.
144
+ *
145
+ * @param context - The actions context.
146
+ * @returns The read action.
147
+ */
148
+ export declare function readOpenRouterPromptFactory(context: OpenRouterPromptServerActionsContext): import("@dereekb/model").TransformAndValidateFunctionResultFunction<ReadOpenRouterPromptParams, (document: OpenRouterPromptDocument) => Promise<ReadOpenRouterPromptResult>, object, unknown>;
134
149
  /**
135
150
  * Creates a new version, allocating its number inside the transaction, locking the version it succeeds,
136
151
  * and optionally promoting it.
@@ -1,5 +1,5 @@
1
1
  import { type Maybe, type Milliseconds } from '@dereekb/util';
2
- import { type OpenRouterPromptDefinition, type OpenRouterPromptKey, type OpenRouterPromptVersionNumber, type OpenRouterResolvedPrompt } from '@dereekb/openrouter';
2
+ import { type OpenRouterPromptDefinition, type OpenRouterPromptKey, type OpenRouterPromptResolutionSource, type OpenRouterPromptVersionNumber, type OpenRouterResolvedPrompt } from '@dereekb/openrouter';
3
3
  import { type OpenRouterPrompt, type OpenRouterPromptFirestoreCollections } from '@dereekb/openrouter/firebase';
4
4
  /**
5
5
  * Error thrown when a prompt cannot be resolved.
@@ -22,6 +22,19 @@ export interface OpenRouterResolvePromptParams {
22
22
  */
23
23
  readonly version?: Maybe<OpenRouterPromptVersionNumber>;
24
24
  }
25
+ /**
26
+ * A resolved prompt together with where it came from.
27
+ */
28
+ export interface OpenRouterPromptResolution {
29
+ /**
30
+ * The version that will be served.
31
+ */
32
+ readonly resolved: OpenRouterResolvedPrompt;
33
+ /**
34
+ * Which half of the resolution produced {@link resolved}.
35
+ */
36
+ readonly source: OpenRouterPromptResolutionSource;
37
+ }
25
38
  /**
26
39
  * Default time a resolved prompt is cached for.
27
40
  *
@@ -65,6 +78,17 @@ export declare abstract class OpenRouterPromptService {
65
78
  * @throws {OpenRouterPromptResolutionError} when the prompt, or the requested version, is not servable.
66
79
  */
67
80
  abstract resolvePrompt(params: OpenRouterResolvePromptParams): Promise<OpenRouterResolvedPrompt>;
81
+ /**
82
+ * Resolves a prompt and reports which half of the resolution served it.
83
+ *
84
+ * The same work {@link resolvePrompt} does, off the same cache — that one projects this result down
85
+ * to its `resolved` half. Separate rather than widening `resolvePrompt`'s return type because every
86
+ * dispatch path calls it on the hot path and wants the prompt, not a wrapper around it; the source is
87
+ * only interesting to a caller inspecting the prompt rather than running it.
88
+ *
89
+ * @throws {OpenRouterPromptResolutionError} when the prompt, or the requested version, is not servable.
90
+ */
91
+ abstract readPrompt(params: OpenRouterResolvePromptParams): Promise<OpenRouterPromptResolution>;
68
92
  /**
69
93
  * Drops any cached resolution for a prompt. Called after a publish/promote so the change is visible
70
94
  * immediately rather than after the cache expires.
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@dereekb/openrouter",
3
- "version": "14.1.0",
3
+ "version": "14.3.0",
4
+ "sideEffects": false,
4
5
  "type": "module",
5
6
  "exports": {
6
7
  "./firebase": {
@@ -21,7 +22,7 @@
21
22
  }
22
23
  },
23
24
  "peerDependencies": {
24
- "@dereekb/util": "14.1.0",
25
+ "@dereekb/util": "14.3.0",
25
26
  "@openrouter/sdk": "^1.2.26"
26
27
  },
27
28
  "module": "./index.esm.js",
@@ -47,6 +47,17 @@ export interface OpenRouterResolvedPrompt {
47
47
  */
48
48
  readonly config: OpenRouterModelConfig;
49
49
  }
50
+ /**
51
+ * Which half of a prompt resolution served it: the stored version, or the code definition standing in
52
+ * for it.
53
+ *
54
+ * `definition` is not an error state — it is the normal answer for an environment that has never been
55
+ * seeded, and for one whose {@link OpenRouterPromptDefinition} has moved ahead of the store.
56
+ *
57
+ * Lives here rather than beside the resolver in `@dereekb/openrouter/firebase-server` because the read
58
+ * API returns it, and that API is declared client-side.
59
+ */
60
+ export type OpenRouterPromptResolutionSource = 'store' | 'definition';
50
61
  /**
51
62
  * A prompt defined in CODE rather than published to Firestore.
52
63
  *