@dereekb/openrouter 13.37.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/LICENSE +21 -0
- package/README.md +195 -0
- package/firebase/index.cjs.default.js +1 -0
- package/firebase/index.cjs.js +666 -0
- package/firebase/index.cjs.mjs +2 -0
- package/firebase/index.d.ts +1 -0
- package/firebase/index.esm.js +626 -0
- package/firebase/package.json +25 -0
- package/firebase/src/index.d.ts +1 -0
- package/firebase/src/lib/index.d.ts +4 -0
- package/firebase/src/lib/openrouter.api.d.ts +226 -0
- package/firebase/src/lib/openrouter.id.d.ts +56 -0
- package/firebase/src/lib/openrouter.model.d.ts +609 -0
- package/firebase/src/lib/openrouter.query.d.ts +121 -0
- package/firebase-server/index.cjs.default.js +1 -0
- package/firebase-server/index.cjs.js +4520 -0
- package/firebase-server/index.cjs.mjs +2 -0
- package/firebase-server/index.d.ts +1 -0
- package/firebase-server/index.esm.js +4466 -0
- package/firebase-server/package.json +38 -0
- package/firebase-server/src/index.d.ts +1 -0
- package/firebase-server/src/lib/index.d.ts +10 -0
- package/firebase-server/src/lib/openrouter.action.server.d.ts +196 -0
- package/firebase-server/src/lib/openrouter.broadcast.d.ts +93 -0
- package/firebase-server/src/lib/openrouter.call.inline.d.ts +57 -0
- package/firebase-server/src/lib/openrouter.file.attachment.d.ts +97 -0
- package/firebase-server/src/lib/openrouter.module.d.ts +65 -0
- package/firebase-server/src/lib/openrouter.prompt.service.d.ts +109 -0
- package/firebase-server/src/lib/openrouter.runtask.handle.d.ts +56 -0
- package/firebase-server/src/lib/openrouter.runtask.service.d.ts +380 -0
- package/firebase-server/src/lib/openrouter.runtask.sweep.d.ts +170 -0
- package/firebase-server/src/lib/openrouter.state.accessor.d.ts +106 -0
- package/firebase-server/src/test/openrouter.fake.d.ts +134 -0
- package/index.cjs.default.js +1 -0
- package/index.cjs.js +1867 -0
- package/index.cjs.mjs +2 -0
- package/index.d.ts +1 -0
- package/index.esm.js +1771 -0
- package/package.json +32 -0
- package/src/index.d.ts +1 -0
- package/src/lib/index.d.ts +10 -0
- package/src/lib/openrouter.call.d.ts +268 -0
- package/src/lib/openrouter.config.d.ts +314 -0
- package/src/lib/openrouter.embedding.d.ts +87 -0
- package/src/lib/openrouter.generation.d.ts +46 -0
- package/src/lib/openrouter.input.d.ts +238 -0
- package/src/lib/openrouter.prompt.d.ts +79 -0
- package/src/lib/openrouter.request.d.ts +91 -0
- package/src/lib/openrouter.sdk.d.ts +37 -0
- package/src/lib/openrouter.tool.d.ts +99 -0
- package/src/lib/openrouter.type.d.ts +125 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dereekb/openrouter/firebase-server",
|
|
3
|
+
"version": "13.37.0",
|
|
4
|
+
"peerDependencies": {
|
|
5
|
+
"@dereekb/analytics": "13.37.0",
|
|
6
|
+
"@dereekb/date": "13.37.0",
|
|
7
|
+
"@dereekb/firebase": "13.37.0",
|
|
8
|
+
"@dereekb/firebase-server": "13.37.0",
|
|
9
|
+
"@dereekb/model": "13.37.0",
|
|
10
|
+
"@dereekb/nestjs": "13.37.0",
|
|
11
|
+
"@dereekb/openrouter": "13.37.0",
|
|
12
|
+
"@dereekb/rxjs": "13.37.0",
|
|
13
|
+
"@dereekb/util": "13.37.0",
|
|
14
|
+
"@nestjs/common": "^11.1.19",
|
|
15
|
+
"@nestjs/config": "^4.0.4",
|
|
16
|
+
"@openrouter/sdk": "^1.2.26",
|
|
17
|
+
"arktype": "^2.2.0",
|
|
18
|
+
"date-fns": "^4.1.0",
|
|
19
|
+
"express": "^5.2.1",
|
|
20
|
+
"firebase-functions": "^7.2.5",
|
|
21
|
+
"make-error": "^1.3.6"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@nestjs/testing": "^11.1.19"
|
|
25
|
+
},
|
|
26
|
+
"exports": {
|
|
27
|
+
"./package.json": "./package.json",
|
|
28
|
+
".": {
|
|
29
|
+
"module": "./index.esm.js",
|
|
30
|
+
"types": "./index.d.ts",
|
|
31
|
+
"import": "./index.cjs.mjs",
|
|
32
|
+
"default": "./index.cjs.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"module": "./index.esm.js",
|
|
36
|
+
"main": "./index.cjs.js",
|
|
37
|
+
"types": "./index.d.ts"
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './openrouter.action.server';
|
|
2
|
+
export * from './openrouter.broadcast';
|
|
3
|
+
export * from './openrouter.call.inline';
|
|
4
|
+
export * from './openrouter.file.attachment';
|
|
5
|
+
export * from './openrouter.module';
|
|
6
|
+
export * from './openrouter.prompt.service';
|
|
7
|
+
export * from './openrouter.runtask.handle';
|
|
8
|
+
export * from './openrouter.runtask.service';
|
|
9
|
+
export * from './openrouter.runtask.sweep';
|
|
10
|
+
export * from './openrouter.state.accessor';
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { type FirebaseServerActionsContext } from '@dereekb/firebase-server';
|
|
2
|
+
import { type FirestoreContextReference } from '@dereekb/firebase';
|
|
3
|
+
import { type Maybe } from '@dereekb/util';
|
|
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';
|
|
6
|
+
import { type OpenRouterPromptService } from './openrouter.prompt.service';
|
|
7
|
+
/**
|
|
8
|
+
* Context required by the OpenRouter prompt server actions.
|
|
9
|
+
*/
|
|
10
|
+
export interface OpenRouterPromptServerActionsContext extends FirebaseServerActionsContext, OpenRouterPromptFirestoreCollections, FirestoreContextReference {
|
|
11
|
+
/**
|
|
12
|
+
* The prompt service, so a publish or promote can drop its cached resolution immediately instead of
|
|
13
|
+
* leaving the change invisible for the cache window.
|
|
14
|
+
*/
|
|
15
|
+
readonly openRouterPromptService: OpenRouterPromptService;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parameters for {@link OpenRouterPromptServerActions.createOpenRouterPrompt}.
|
|
19
|
+
*
|
|
20
|
+
* Server-side only, and deliberately not part of the model API: a prompt comes into existence from a
|
|
21
|
+
* seed run against an {@link OpenRouterPromptDefinition} the code already carries, so there is no
|
|
22
|
+
* external caller whose input needs validating.
|
|
23
|
+
*/
|
|
24
|
+
export interface CreateOpenRouterPromptParams {
|
|
25
|
+
/**
|
|
26
|
+
* The prompt key, used as the document id. Lowercase, dash-separated by convention.
|
|
27
|
+
*/
|
|
28
|
+
readonly key: OpenRouterPromptKey;
|
|
29
|
+
/**
|
|
30
|
+
* Human-readable name.
|
|
31
|
+
*/
|
|
32
|
+
readonly name: string;
|
|
33
|
+
/**
|
|
34
|
+
* What the prompt is for.
|
|
35
|
+
*/
|
|
36
|
+
readonly description?: Maybe<string>;
|
|
37
|
+
/**
|
|
38
|
+
* Grouping tags.
|
|
39
|
+
*/
|
|
40
|
+
readonly tags?: Maybe<string[]>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parameters for {@link OpenRouterPromptServerActions.seedOpenRouterPrompts}.
|
|
44
|
+
*
|
|
45
|
+
* Server-side only, and deliberately not part of the model API, for the same reason
|
|
46
|
+
* {@link CreateOpenRouterPromptParams} is not: the only input is a filter over keys the code itself
|
|
47
|
+
* declares, and every caller is server-side. Seeding is also not CRUD on `openRouterPrompt` — exposing
|
|
48
|
+
* it over callModel would hand any admin a button that rewrites prompt pointers.
|
|
49
|
+
*/
|
|
50
|
+
export interface SeedOpenRouterPromptsParams {
|
|
51
|
+
/**
|
|
52
|
+
* Restrict the run to these keys. Omit to seed every definition the prompt service carries.
|
|
53
|
+
*/
|
|
54
|
+
readonly promptKeys?: Maybe<OpenRouterPromptKey[]>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Result of {@link OpenRouterPromptServerActions.seedOpenRouterPrompts}.
|
|
58
|
+
*
|
|
59
|
+
* The counts partition the run: `considered === versionsPublished + upToDate + skipped`.
|
|
60
|
+
*/
|
|
61
|
+
export interface SeedOpenRouterPromptsResult {
|
|
62
|
+
/**
|
|
63
|
+
* Definitions looked at, after filtering.
|
|
64
|
+
*/
|
|
65
|
+
readonly considered: number;
|
|
66
|
+
/**
|
|
67
|
+
* Prompt documents created. Always `<= versionsPublished`, since creating one always publishes its
|
|
68
|
+
* declared version too.
|
|
69
|
+
*/
|
|
70
|
+
readonly promptsCreated: number;
|
|
71
|
+
/**
|
|
72
|
+
* Versions written at their declared number.
|
|
73
|
+
*/
|
|
74
|
+
readonly versionsPublished: number;
|
|
75
|
+
/**
|
|
76
|
+
* Definitions whose declared number the store already carried — the steady state, so a scheduled
|
|
77
|
+
* reseed reports every definition here once it has converged.
|
|
78
|
+
*/
|
|
79
|
+
readonly upToDate: number;
|
|
80
|
+
/**
|
|
81
|
+
* The only drift alarm: a definition the seed refused to write. Either the store advanced past it,
|
|
82
|
+
* something else already wrote the number it declares, or the prompt is ARCHIVED. Recovery is to bump
|
|
83
|
+
* the definition's version, which works precisely because the seed pins rather than allocates.
|
|
84
|
+
*/
|
|
85
|
+
readonly skipped: number;
|
|
86
|
+
/**
|
|
87
|
+
* Config warnings raised while validating the definitions, each prefixed with its prompt key.
|
|
88
|
+
*/
|
|
89
|
+
readonly warnings: string[];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Server actions for managing prompts.
|
|
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}.
|
|
98
|
+
*/
|
|
99
|
+
export declare abstract class OpenRouterPromptServerActions {
|
|
100
|
+
abstract createOpenRouterPrompt(params: CreateOpenRouterPromptParams): Promise<OpenRouterPromptDocument>;
|
|
101
|
+
abstract updateOpenRouterPrompt(params: UpdateOpenRouterPromptParams): Promise<(document: OpenRouterPromptDocument) => Promise<OpenRouterPromptDocument>>;
|
|
102
|
+
abstract createOpenRouterPromptVersion(params: CreateOpenRouterPromptVersionParams): Promise<(document: OpenRouterPromptDocument) => Promise<CreateOpenRouterPromptVersionResult>>;
|
|
103
|
+
abstract updateOpenRouterPromptVersion(params: UpdateOpenRouterPromptVersionParams): Promise<(document: OpenRouterPromptVersionDocument) => Promise<UpdateOpenRouterPromptVersionResult>>;
|
|
104
|
+
/**
|
|
105
|
+
* Uncurried, like {@link createOpenRouterPrompt}: a seed has no target document to curry over — the
|
|
106
|
+
* documents it writes are the ones it is deciding whether to create.
|
|
107
|
+
*/
|
|
108
|
+
abstract seedOpenRouterPrompts(params: SeedOpenRouterPromptsParams): Promise<SeedOpenRouterPromptsResult>;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Creates the {@link OpenRouterPromptServerActions}.
|
|
112
|
+
*
|
|
113
|
+
* @param context - The actions context.
|
|
114
|
+
* @returns The server actions.
|
|
115
|
+
*/
|
|
116
|
+
export declare function openRouterPromptServerActions(context: OpenRouterPromptServerActionsContext): OpenRouterPromptServerActions;
|
|
117
|
+
/**
|
|
118
|
+
* Creates a new prompt at its caller-supplied key, refusing a key that already exists.
|
|
119
|
+
*
|
|
120
|
+
* Takes its params directly rather than through an arktype-validated action: the only callers are a
|
|
121
|
+
* seed and a test, both of them server-side.
|
|
122
|
+
*
|
|
123
|
+
* @param context - The actions context.
|
|
124
|
+
* @returns The create action.
|
|
125
|
+
*/
|
|
126
|
+
export declare function createOpenRouterPromptFactory(context: OpenRouterPromptServerActionsContext): (params: CreateOpenRouterPromptParams) => Promise<OpenRouterPromptDocument>;
|
|
127
|
+
/**
|
|
128
|
+
* Updates a prompt's metadata, lifecycle state, or active version, then drops its cached resolution.
|
|
129
|
+
*
|
|
130
|
+
* @param context - The actions context.
|
|
131
|
+
* @returns The update action.
|
|
132
|
+
*/
|
|
133
|
+
export declare function updateOpenRouterPromptFactory(context: OpenRouterPromptServerActionsContext): import("@dereekb/model").TransformAndValidateFunctionResultFunction<UpdateOpenRouterPromptParams, (document: OpenRouterPromptDocument) => Promise<OpenRouterPromptDocument>, object, unknown>;
|
|
134
|
+
/**
|
|
135
|
+
* Creates a new version, allocating its number inside the transaction, locking the version it succeeds,
|
|
136
|
+
* and optionally promoting it.
|
|
137
|
+
*
|
|
138
|
+
* @param context - The actions context.
|
|
139
|
+
* @returns The create version action.
|
|
140
|
+
*/
|
|
141
|
+
export declare function createOpenRouterPromptVersionFactory(context: OpenRouterPromptServerActionsContext): import("@dereekb/model").TransformAndValidateFunctionResultFunction<CreateOpenRouterPromptVersionParams, (document: OpenRouterPromptDocument) => Promise<CreateOpenRouterPromptVersionResult>, object, unknown>;
|
|
142
|
+
/**
|
|
143
|
+
* Edits the latest version of a prompt in place, refusing one the next version has locked.
|
|
144
|
+
*
|
|
145
|
+
* The lock is re-read inside the transaction rather than trusted from the caller's snapshot: a create
|
|
146
|
+
* racing this edit would otherwise let the write land on a version that had just stopped being the head.
|
|
147
|
+
*
|
|
148
|
+
* @param context - The actions context.
|
|
149
|
+
* @returns The update version action.
|
|
150
|
+
*/
|
|
151
|
+
export declare function updateOpenRouterPromptVersionFactory(context: OpenRouterPromptServerActionsContext): import("@dereekb/model").TransformAndValidateFunctionResultFunction<UpdateOpenRouterPromptVersionParams, (document: OpenRouterPromptVersionDocument) => Promise<UpdateOpenRouterPromptVersionResult>, object, unknown>;
|
|
152
|
+
/**
|
|
153
|
+
* How many definitions {@link seedOpenRouterPromptsFactory} publishes at a time.
|
|
154
|
+
*
|
|
155
|
+
* Each definition's writes are their own commit against documents no other definition touches, so the
|
|
156
|
+
* bound is not a correctness requirement — it keeps a large registry from opening one connection per
|
|
157
|
+
* prompt against Firestore all at once.
|
|
158
|
+
*/
|
|
159
|
+
export declare const OPENROUTER_SEED_PROMPTS_MAX_PARALLEL_UPDATES = 25;
|
|
160
|
+
/**
|
|
161
|
+
* Publishes each code definition the prompt service carries at ITS OWN declared version number.
|
|
162
|
+
*
|
|
163
|
+
* The number is a compile-time constant rather than one allocated from `lv`, which is the whole point:
|
|
164
|
+
* an auto-allocating writer cannot converge on a number chosen in code, because its write address moves
|
|
165
|
+
* with its own effect — so a store seeded by {@link createOpenRouterPromptVersionFactory} sits
|
|
166
|
+
* permanently behind the definition and the resolver stands in for it forever. Pinning makes the write
|
|
167
|
+
* address a pure function of the definition, so a re-seed is a no-op rather than a step toward
|
|
168
|
+
* convergence, and a scheduled reseed can keep the store in step with deploys.
|
|
169
|
+
*
|
|
170
|
+
* Publishing is gated on `lv` rather than `av`: `lv` is the allocator and is monotone, while `av` is a
|
|
171
|
+
* pointer an operator can move backwards — gating on it would let a demotion re-trigger a publish on
|
|
172
|
+
* every scheduled tick, forever.
|
|
173
|
+
*
|
|
174
|
+
* Deliberately NOT built on {@link createOpenRouterPromptVersionFactory}: that action is reachable over
|
|
175
|
+
* callModel/MCP, and its in-transaction allocation is a race-safety contract on a public surface rather
|
|
176
|
+
* than an implementation detail. A seed needs no allocator at all, because the number it writes is a
|
|
177
|
+
* compile-time constant — something `CreateOpenRouterPromptVersionParams` cannot assert about an
|
|
178
|
+
* arbitrary caller.
|
|
179
|
+
*
|
|
180
|
+
* No transaction, at any scope. A version document carries a whole prompt's text, so a registry of them
|
|
181
|
+
* can put megabytes through a single commit, and a commit — transaction or write batch alike — is capped
|
|
182
|
+
* around 10MB. The run is instead one read pass over every prompt document, then each definition
|
|
183
|
+
* published independently through its OWN write batch, at most
|
|
184
|
+
* {@link OPENROUTER_SEED_PROMPTS_MAX_PARALLEL_UPDATES} at a time. The batch is what keeps a publish
|
|
185
|
+
* atomic — the version, the lock on the head it replaces, and the pointer move land together or not at
|
|
186
|
+
* all — while holding any one commit to the three documents a single definition touches.
|
|
187
|
+
*
|
|
188
|
+
* What that costs is the transaction's read set: the check that nothing else already holds the number
|
|
189
|
+
* being written is now a read taken shortly before the commit rather than a conflict detected at it.
|
|
190
|
+
* Losing that window means a seed overwrites a version an operator published under the same number in
|
|
191
|
+
* the same instant — narrow enough to trade for a run whose size cannot refuse it.
|
|
192
|
+
*
|
|
193
|
+
* @param context - The actions context.
|
|
194
|
+
* @returns The seed action.
|
|
195
|
+
*/
|
|
196
|
+
export declare function seedOpenRouterPromptsFactory(context: OpenRouterPromptServerActionsContext): (params: SeedOpenRouterPromptsParams) => Promise<SeedOpenRouterPromptsResult>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { type Maybe } from '@dereekb/util';
|
|
2
|
+
import { type OpenRouterRunTaskKey, type OpenRouterRunUsage } from '@dereekb/openrouter';
|
|
3
|
+
import { type OpenRouterRunTaskFirestoreCollections } from '@dereekb/openrouter/firebase';
|
|
4
|
+
/**
|
|
5
|
+
* The generation fields a broadcast span carries.
|
|
6
|
+
*
|
|
7
|
+
* Structurally the `OpenRouterGenerationInfo` that `@dereekb/nestjs/openrouter` already extracts from an
|
|
8
|
+
* OTLP span; declared independently so this package does not depend on the NestJS entry just for a shape.
|
|
9
|
+
*/
|
|
10
|
+
export interface OpenRouterBroadcastGenerationInfo {
|
|
11
|
+
readonly generationId?: Maybe<string>;
|
|
12
|
+
readonly model?: Maybe<string>;
|
|
13
|
+
readonly provider?: Maybe<string>;
|
|
14
|
+
readonly promptTokens?: Maybe<number>;
|
|
15
|
+
readonly completionTokens?: Maybe<number>;
|
|
16
|
+
readonly totalTokens?: Maybe<number>;
|
|
17
|
+
readonly cost?: Maybe<number>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The attribute key OpenRouter surfaces a `trace: { runTaskKey }` property under.
|
|
21
|
+
*
|
|
22
|
+
* OpenRouter's docs: "custom metadata from the `trace` field is included as span attributes in the OTLP
|
|
23
|
+
* JSON payload". The exact prefix is not contractual, so several candidates are checked.
|
|
24
|
+
*/
|
|
25
|
+
export declare const OPENROUTER_BROADCAST_RUN_TASK_KEY_ATTRIBUTES: readonly string[];
|
|
26
|
+
/**
|
|
27
|
+
* Reads the run task key out of a span's attribute map.
|
|
28
|
+
*
|
|
29
|
+
* @param attributes - The flattened span attributes.
|
|
30
|
+
* @returns The run task key, when the span carries one.
|
|
31
|
+
*/
|
|
32
|
+
export declare function openRouterRunTaskKeyFromBroadcastAttributes(attributes: Maybe<Map<string, string | number | boolean>>): Maybe<OpenRouterRunTaskKey>;
|
|
33
|
+
/**
|
|
34
|
+
* Params for {@link reconcileOpenRouterRunTaskFromBroadcast}.
|
|
35
|
+
*/
|
|
36
|
+
export interface ReconcileOpenRouterRunTaskFromBroadcastParams {
|
|
37
|
+
/**
|
|
38
|
+
* The run task collections.
|
|
39
|
+
*/
|
|
40
|
+
readonly collections: OpenRouterRunTaskFirestoreCollections;
|
|
41
|
+
/**
|
|
42
|
+
* The run task the span belongs to.
|
|
43
|
+
*/
|
|
44
|
+
readonly key: Maybe<OpenRouterRunTaskKey>;
|
|
45
|
+
/**
|
|
46
|
+
* The generation info extracted from the span.
|
|
47
|
+
*/
|
|
48
|
+
readonly generation: OpenRouterBroadcastGenerationInfo;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Result of a reconciliation attempt.
|
|
52
|
+
*/
|
|
53
|
+
export interface ReconcileOpenRouterRunTaskFromBroadcastResult {
|
|
54
|
+
/**
|
|
55
|
+
* Whether a run task was found and updated.
|
|
56
|
+
*/
|
|
57
|
+
readonly reconciled: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Reconciles generation id, usage, and cost onto a run task from a broadcast span.
|
|
61
|
+
*
|
|
62
|
+
* This exists for the one thing the runner cannot always know: cost is finalised server-side, so the
|
|
63
|
+
* value the runner wrote from the response can be provisional.
|
|
64
|
+
*
|
|
65
|
+
* TELEMETRY ONLY — it never changes `s`, and a span it cannot match is dropped silently. Broadcast is
|
|
66
|
+
* enabled account-wide rather than per request, delivery is asynchronous and best-effort, and a dropped
|
|
67
|
+
* trace must not be able to strand a task. Putting control flow here would make a missing trace look
|
|
68
|
+
* exactly like a stuck run.
|
|
69
|
+
*
|
|
70
|
+
* @param params - The collections, run task key, and generation info.
|
|
71
|
+
* @returns Whether anything was written.
|
|
72
|
+
*/
|
|
73
|
+
export declare function reconcileOpenRouterRunTaskFromBroadcast(params: ReconcileOpenRouterRunTaskFromBroadcastParams): Promise<ReconcileOpenRouterRunTaskFromBroadcastResult>;
|
|
74
|
+
/**
|
|
75
|
+
* Merges broadcast-reported usage into the usage already stored on a task.
|
|
76
|
+
*
|
|
77
|
+
* The broadcast value wins where it is present — it is the later, authoritative measurement — and the
|
|
78
|
+
* stored value is kept where the span is silent, so an incomplete span cannot erase what the runner knew.
|
|
79
|
+
*
|
|
80
|
+
* The filter MUST be `NULL`, not `mergeObjects`' default of `UNDEFINED`. Every field here is a `Maybe`, so a
|
|
81
|
+
* span reporting `cost: null` means "did not measure" — and under the default filter that `null` would win
|
|
82
|
+
* and erase the cost the runner actually recorded.
|
|
83
|
+
*
|
|
84
|
+
* `reasoningTokens` / `cachedTokens` / `isByok` are absent from the literal because a broadcast span carries
|
|
85
|
+
* none of them; they ride along from `existing` untouched.
|
|
86
|
+
*
|
|
87
|
+
* @param existing - Usage already stored.
|
|
88
|
+
* @param generation - The span's generation info.
|
|
89
|
+
* @returns The merged usage.
|
|
90
|
+
*
|
|
91
|
+
* @__NO_SIDE_EFFECTS__
|
|
92
|
+
*/
|
|
93
|
+
export declare function mergeOpenRouterRunUsage(existing: Maybe<OpenRouterRunUsage>, generation: OpenRouterBroadcastGenerationInfo): OpenRouterRunUsage;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Maybe } from '@dereekb/util';
|
|
2
|
+
import { type OpenRouterAttachedFileReference, type OpenRouterCallResult, type OpenRouterCore, type OpenRouterInput, type OpenRouterModelConfig, type OpenRouterPromptKey, type OpenRouterPromptVersionNumber, type Tool } from '@dereekb/openrouter';
|
|
3
|
+
import { type OpenRouterPromptService } from './openrouter.prompt.service';
|
|
4
|
+
/**
|
|
5
|
+
* Params for {@link callModelForPrompt}.
|
|
6
|
+
*/
|
|
7
|
+
export interface CallModelForPromptParams {
|
|
8
|
+
/**
|
|
9
|
+
* The OpenRouter client.
|
|
10
|
+
*/
|
|
11
|
+
readonly client: OpenRouterCore;
|
|
12
|
+
/**
|
|
13
|
+
* The prompt service used to resolve the version.
|
|
14
|
+
*/
|
|
15
|
+
readonly promptService: OpenRouterPromptService;
|
|
16
|
+
/**
|
|
17
|
+
* The prompt to run.
|
|
18
|
+
*/
|
|
19
|
+
readonly promptKey: OpenRouterPromptKey;
|
|
20
|
+
/**
|
|
21
|
+
* Version to pin. Omit to use the prompt's active version.
|
|
22
|
+
*/
|
|
23
|
+
readonly version?: Maybe<OpenRouterPromptVersionNumber>;
|
|
24
|
+
/**
|
|
25
|
+
* The call input.
|
|
26
|
+
*/
|
|
27
|
+
readonly input?: Maybe<OpenRouterInput>;
|
|
28
|
+
/**
|
|
29
|
+
* Per-call config overrides.
|
|
30
|
+
*/
|
|
31
|
+
readonly configOverrides?: Maybe<OpenRouterModelConfig>;
|
|
32
|
+
/**
|
|
33
|
+
* Files, already attached for this call — see `openRouterFileAttachmentResolver`.
|
|
34
|
+
*
|
|
35
|
+
* Attached rather than by path, unlike the queued path: an inline call runs once, right now, so there
|
|
36
|
+
* is no later attempt for which a url could have expired.
|
|
37
|
+
*/
|
|
38
|
+
readonly files?: Maybe<OpenRouterAttachedFileReference[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Client-side tools.
|
|
41
|
+
*/
|
|
42
|
+
readonly tools?: Maybe<readonly Tool[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Trace metadata for cost/usage reconciliation.
|
|
45
|
+
*/
|
|
46
|
+
readonly trace?: Maybe<Record<string, unknown>>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Runs a prompt INLINE and returns the result, with no run task document.
|
|
50
|
+
*
|
|
51
|
+
* Use this wherever a call reliably finishes in a few seconds. The queue exists for calls that do not,
|
|
52
|
+
* and paying for a document, a lease, and a sweep interval on a two-second call buys nothing.
|
|
53
|
+
*
|
|
54
|
+
* @param params - The client, prompt service, prompt, and input.
|
|
55
|
+
* @returns The normalized call result.
|
|
56
|
+
*/
|
|
57
|
+
export declare function callModelForPrompt(params: CallModelForPromptParams): Promise<OpenRouterCallResult>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { type FirebaseStorageContext } from '@dereekb/firebase';
|
|
2
|
+
import { type FirebaseServerEnvService } from '@dereekb/firebase-server';
|
|
3
|
+
import { type Maybe, type Milliseconds } from '@dereekb/util';
|
|
4
|
+
import { type OpenRouterAttachedFileReference, type OpenRouterFileReference } from '@dereekb/openrouter';
|
|
5
|
+
/**
|
|
6
|
+
* How a file is carried to OpenRouter on one attempt.
|
|
7
|
+
*
|
|
8
|
+
* - `signedUrl` — a short-lived signed url OpenRouter dereferences itself. Cheap, and the default.
|
|
9
|
+
* - `inlineData` — the bytes, base64'd into the request body. The only option when the object is not
|
|
10
|
+
* reachable from the public internet, which is exactly the case against the Firebase storage
|
|
11
|
+
* emulator: signing is unsupported there, so the accessor falls back to a `publicUrl()` on localhost.
|
|
12
|
+
*/
|
|
13
|
+
export type OpenRouterFileAttachmentMode = 'signedUrl' | 'inlineData';
|
|
14
|
+
/**
|
|
15
|
+
* Default lifetime of a signed url minted for one attempt.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately short. The url only has to survive the single request it is attached to, and every
|
|
18
|
+
* attempt gets a freshly signed one — so a short TTL costs nothing and narrows the window in which a
|
|
19
|
+
* third party holds a bearer credential to the object.
|
|
20
|
+
*/
|
|
21
|
+
export declare const DEFAULT_OPENROUTER_SIGNED_URL_TTL: Milliseconds;
|
|
22
|
+
/**
|
|
23
|
+
* Cap on the size of a file carried inline.
|
|
24
|
+
*
|
|
25
|
+
* Base64 inflates the payload by roughly a third, and the cost is re-paid on EVERY attempt rather than
|
|
26
|
+
* amortized the way a url is — so this is a guard against turning a retry loop into a bandwidth bill,
|
|
27
|
+
* not a correctness limit.
|
|
28
|
+
*/
|
|
29
|
+
export declare const DEFAULT_OPENROUTER_MAX_INLINE_FILE_SIZE_BYTES: number;
|
|
30
|
+
/**
|
|
31
|
+
* Content type assumed for an inlined object whose metadata reports none.
|
|
32
|
+
*
|
|
33
|
+
* Matches {@link openRouterInputFileDataPart}'s own default, so the two cannot disagree.
|
|
34
|
+
*/
|
|
35
|
+
export declare const DEFAULT_OPENROUTER_INLINE_FILE_CONTENT_TYPE = "application/pdf";
|
|
36
|
+
/**
|
|
37
|
+
* Config for {@link openRouterFileAttachmentResolver}.
|
|
38
|
+
*/
|
|
39
|
+
export interface OpenRouterFileAttachmentResolverConfig {
|
|
40
|
+
/**
|
|
41
|
+
* Storage context the files are read from. Required only when files are actually attached.
|
|
42
|
+
*/
|
|
43
|
+
readonly storageContext?: Maybe<FirebaseStorageContext>;
|
|
44
|
+
/**
|
|
45
|
+
* Selects the mode when `mode` is not given: a non-production environment picks `inlineData`.
|
|
46
|
+
*
|
|
47
|
+
* This is the whole environment gate. An app wires its env service in once and both the emulator and
|
|
48
|
+
* production do the right thing without a second switch to keep in sync.
|
|
49
|
+
*/
|
|
50
|
+
readonly envService?: Maybe<FirebaseServerEnvService>;
|
|
51
|
+
/**
|
|
52
|
+
* Explicit mode override. Wins over `envService`.
|
|
53
|
+
*
|
|
54
|
+
* For a unit test that wants one specific transport, or an app that knows its objects are (or are
|
|
55
|
+
* not) publicly reachable regardless of environment.
|
|
56
|
+
*/
|
|
57
|
+
readonly mode?: Maybe<OpenRouterFileAttachmentMode>;
|
|
58
|
+
/**
|
|
59
|
+
* Signed-url lifetime. Defaults to {@link DEFAULT_OPENROUTER_SIGNED_URL_TTL}.
|
|
60
|
+
*/
|
|
61
|
+
readonly signedUrlTtl?: Maybe<Milliseconds>;
|
|
62
|
+
/**
|
|
63
|
+
* Inline size cap. Defaults to {@link DEFAULT_OPENROUTER_MAX_INLINE_FILE_SIZE_BYTES}.
|
|
64
|
+
*/
|
|
65
|
+
readonly maxInlineFileSizeBytes?: Maybe<number>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resolves file references into the attachments to send on ONE attempt.
|
|
69
|
+
*/
|
|
70
|
+
export type OpenRouterFileAttachmentResolver = (files: Maybe<OpenRouterFileReference[]>) => Promise<OpenRouterAttachedFileReference[]>;
|
|
71
|
+
/**
|
|
72
|
+
* The mode a config resolves to.
|
|
73
|
+
*
|
|
74
|
+
* Order: the explicit override, then the environment service, then `signedUrl`.
|
|
75
|
+
*
|
|
76
|
+
* Gated on `isProduction` rather than `isTestingEnv`, because the question is whether OpenRouter can
|
|
77
|
+
* reach the object, not whether this is a test. Every non-production environment here is a localhost
|
|
78
|
+
* one — the emulator under `nx serve` as much as a spec run — and `isTestingEnv` is only
|
|
79
|
+
* `NODE_ENV === 'test'`, so it covered the spec run and left the emulator handing OpenRouter a
|
|
80
|
+
* `127.0.0.1` url that it rejects outright.
|
|
81
|
+
*
|
|
82
|
+
* @param config - The resolver config.
|
|
83
|
+
* @returns The mode.
|
|
84
|
+
*
|
|
85
|
+
* @__NO_SIDE_EFFECTS__
|
|
86
|
+
*/
|
|
87
|
+
export declare function openRouterFileAttachmentModeForConfig(config: OpenRouterFileAttachmentResolverConfig): OpenRouterFileAttachmentMode;
|
|
88
|
+
/**
|
|
89
|
+
* Creates an {@link OpenRouterFileAttachmentResolver}.
|
|
90
|
+
*
|
|
91
|
+
* Called once per attempt, never at enqueue time: both transports produce something that is only valid
|
|
92
|
+
* for the request it is attached to.
|
|
93
|
+
*
|
|
94
|
+
* @param config - The storage context, environment service, and transport settings.
|
|
95
|
+
* @returns The resolver.
|
|
96
|
+
*/
|
|
97
|
+
export declare function openRouterFileAttachmentResolver(config: OpenRouterFileAttachmentResolverConfig): OpenRouterFileAttachmentResolver;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type DynamicModule, type ModuleMetadata, type Provider } from '@nestjs/common';
|
|
2
|
+
import { type OpenRouterPromptService } from './openrouter.prompt.service';
|
|
3
|
+
import { type OpenRouterRunTaskService } from './openrouter.runtask.service';
|
|
4
|
+
/**
|
|
5
|
+
* Config for {@link appOpenRouterModuleMetadata}.
|
|
6
|
+
*/
|
|
7
|
+
export interface AppOpenRouterModuleMetadataConfig {
|
|
8
|
+
/**
|
|
9
|
+
* Module providing the concrete {@link OpenRouterPromptService},
|
|
10
|
+
* {@link OpenRouterRunTaskService}, and the actions context.
|
|
11
|
+
*
|
|
12
|
+
* Kept as a caller-supplied dependency module rather than constructed here: the run-task service needs
|
|
13
|
+
* the app's Firestore collections, its storage context, its OpenRouter client, and its
|
|
14
|
+
* terminal-state handler — every one of which is app-specific.
|
|
15
|
+
*/
|
|
16
|
+
readonly dependencyModule: Required<ModuleMetadata>['imports'][0];
|
|
17
|
+
/**
|
|
18
|
+
* Provider for {@link OpenRouterPromptServerActions}.
|
|
19
|
+
*/
|
|
20
|
+
readonly serverActionsProvider: Provider;
|
|
21
|
+
/**
|
|
22
|
+
* Additional providers to expose.
|
|
23
|
+
*/
|
|
24
|
+
readonly providers?: Provider[];
|
|
25
|
+
/**
|
|
26
|
+
* Additional exports.
|
|
27
|
+
*/
|
|
28
|
+
readonly exports?: Required<ModuleMetadata>['exports'];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Builds the module metadata for an app's OpenRouter module.
|
|
32
|
+
*
|
|
33
|
+
* @param config - The dependency module and server-actions provider.
|
|
34
|
+
* @returns The module metadata.
|
|
35
|
+
*/
|
|
36
|
+
export declare function appOpenRouterModuleMetadata(config: AppOpenRouterModuleMetadataConfig): ModuleMetadata;
|
|
37
|
+
/**
|
|
38
|
+
* Base class an app's OpenRouter module extends.
|
|
39
|
+
*/
|
|
40
|
+
export declare abstract class AbstractAppOpenRouterModule {
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A reference to an {@link OpenRouterPromptService}, for an actions context to extend.
|
|
44
|
+
*/
|
|
45
|
+
export interface OpenRouterPromptServiceRef {
|
|
46
|
+
readonly openRouterPromptService: OpenRouterPromptService;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A reference to an {@link OpenRouterRunTaskService}, for an actions context to extend.
|
|
50
|
+
*/
|
|
51
|
+
export interface OpenRouterRunTaskServiceRef {
|
|
52
|
+
readonly openRouterRunTaskService: OpenRouterRunTaskService;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Injection token for the {@link OpenRouterPromptService}.
|
|
56
|
+
*/
|
|
57
|
+
export declare const OPENROUTER_PROMPT_SERVICE_TOKEN = "OPENROUTER_PROMPT_SERVICE_TOKEN";
|
|
58
|
+
/**
|
|
59
|
+
* Injection token for the {@link OpenRouterRunTaskService}.
|
|
60
|
+
*/
|
|
61
|
+
export declare const OPENROUTER_RUN_TASK_SERVICE_TOKEN = "OPENROUTER_RUN_TASK_SERVICE_TOKEN";
|
|
62
|
+
/**
|
|
63
|
+
* Type of a dynamic module produced for the OpenRouter services.
|
|
64
|
+
*/
|
|
65
|
+
export type OpenRouterDynamicModule = DynamicModule;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { type Maybe, type Milliseconds } from '@dereekb/util';
|
|
2
|
+
import { type OpenRouterPromptDefinition, type OpenRouterPromptKey, type OpenRouterPromptVersionNumber, type OpenRouterResolvedPrompt } from '@dereekb/openrouter';
|
|
3
|
+
import { type OpenRouterPrompt, type OpenRouterPromptFirestoreCollections } from '@dereekb/openrouter/firebase';
|
|
4
|
+
/**
|
|
5
|
+
* Error thrown when a prompt cannot be resolved.
|
|
6
|
+
*/
|
|
7
|
+
export declare class OpenRouterPromptResolutionError extends Error {
|
|
8
|
+
readonly promptKey: OpenRouterPromptKey;
|
|
9
|
+
readonly version: Maybe<OpenRouterPromptVersionNumber>;
|
|
10
|
+
constructor(promptKey: OpenRouterPromptKey, message: string, version?: Maybe<OpenRouterPromptVersionNumber>);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Params for resolving a prompt version.
|
|
14
|
+
*/
|
|
15
|
+
export interface OpenRouterResolvePromptParams {
|
|
16
|
+
/**
|
|
17
|
+
* The prompt to resolve.
|
|
18
|
+
*/
|
|
19
|
+
readonly promptKey: OpenRouterPromptKey;
|
|
20
|
+
/**
|
|
21
|
+
* The version to pin to. When omitted the prompt's `activeVersion` is used.
|
|
22
|
+
*/
|
|
23
|
+
readonly version?: Maybe<OpenRouterPromptVersionNumber>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Default time a resolved prompt is cached for.
|
|
27
|
+
*
|
|
28
|
+
* What can go stale is which version is active, and what the latest — still editable — version says.
|
|
29
|
+
* Neither is critical to serve to the second: a run that used the previous text for a few minutes is a
|
|
30
|
+
* run against a prompt that was live a few minutes ago, not a wrong run. Bounded rather than permanent
|
|
31
|
+
* is the requirement, so the change lands on its own rather than at the next cold start.
|
|
32
|
+
*/
|
|
33
|
+
export declare const OPENROUTER_PROMPT_CACHE_DURATION: Milliseconds;
|
|
34
|
+
/**
|
|
35
|
+
* Loads prompts and resolves their versions.
|
|
36
|
+
*/
|
|
37
|
+
export declare abstract class OpenRouterPromptService {
|
|
38
|
+
/**
|
|
39
|
+
* Loads the prompt definitions this service resolves against, de-duplicated by key.
|
|
40
|
+
*
|
|
41
|
+
* Exposed so a seeder publishes the exact values the resolver would otherwise stand in with, rather
|
|
42
|
+
* than a second registry wired in parallel that can drift from this one. De-duplicated because
|
|
43
|
+
* `definitionsByKey` is what resolution actually reads: `arrayToMap` is last-wins, so iterating the
|
|
44
|
+
* raw config array would let a seeder publish a definition that never resolves.
|
|
45
|
+
*
|
|
46
|
+
* Async even though the configured implementation answers from memory: a later one may read its
|
|
47
|
+
* registry from somewhere the process does not already hold it — a manifest in storage, another
|
|
48
|
+
* service — and a getter is the one shape that cannot be widened to cover that without breaking
|
|
49
|
+
* every caller.
|
|
50
|
+
*/
|
|
51
|
+
abstract loadPromptDefinitions(): Promise<OpenRouterPromptDefinition[]>;
|
|
52
|
+
/**
|
|
53
|
+
* Loads a prompt document by key.
|
|
54
|
+
*
|
|
55
|
+
* Reads the STORE only — a prompt that exists solely as a code definition has no document, so this
|
|
56
|
+
* returns undefined for it while {@link resolvePrompt} still serves it.
|
|
57
|
+
*/
|
|
58
|
+
abstract loadPrompt(promptKey: OpenRouterPromptKey): Promise<Maybe<OpenRouterPrompt>>;
|
|
59
|
+
/**
|
|
60
|
+
* Resolves the version to serve for a prompt, pinned or active.
|
|
61
|
+
*
|
|
62
|
+
* Resolves from the store, falling back to a configured {@link OpenRouterPromptDefinition} when the
|
|
63
|
+
* store cannot serve or is behind it.
|
|
64
|
+
*
|
|
65
|
+
* @throws {OpenRouterPromptResolutionError} when the prompt, or the requested version, is not servable.
|
|
66
|
+
*/
|
|
67
|
+
abstract resolvePrompt(params: OpenRouterResolvePromptParams): Promise<OpenRouterResolvedPrompt>;
|
|
68
|
+
/**
|
|
69
|
+
* Drops any cached resolution for a prompt. Called after a publish/promote so the change is visible
|
|
70
|
+
* immediately rather than after the cache expires.
|
|
71
|
+
*/
|
|
72
|
+
abstract clearCachedPrompt(promptKey: OpenRouterPromptKey): void;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Config for {@link openRouterPromptService}.
|
|
76
|
+
*/
|
|
77
|
+
export interface OpenRouterPromptServiceConfig {
|
|
78
|
+
/**
|
|
79
|
+
* The prompt collections.
|
|
80
|
+
*/
|
|
81
|
+
readonly collections: OpenRouterPromptFirestoreCollections;
|
|
82
|
+
/**
|
|
83
|
+
* Prompts defined in code, served when the stored prompt cannot serve or is behind them.
|
|
84
|
+
*
|
|
85
|
+
* See {@link OpenRouterPromptDefinition} for the precedence rules. Omit to resolve from Firestore
|
|
86
|
+
* only, which is the behaviour of a service configured without definitions.
|
|
87
|
+
*/
|
|
88
|
+
readonly definitions?: Maybe<OpenRouterPromptDefinition[]>;
|
|
89
|
+
/**
|
|
90
|
+
* How long a resolution is cached for. Defaults to {@link OPENROUTER_PROMPT_CACHE_DURATION}.
|
|
91
|
+
*/
|
|
92
|
+
readonly cacheDuration?: Maybe<Milliseconds>;
|
|
93
|
+
/**
|
|
94
|
+
* Whether a resolved config that fails validation is rejected instead of returned.
|
|
95
|
+
*
|
|
96
|
+
* Defaults to false: a warning-level problem (an unpinned PDF engine, say) should not take a
|
|
97
|
+
* production path down, and an error-level one will fail the request anyway with a clearer message
|
|
98
|
+
* from OpenRouter. Turn it on in tests and in prompt-authoring tooling, where failing early is the
|
|
99
|
+
* point.
|
|
100
|
+
*/
|
|
101
|
+
readonly rejectInvalidConfig?: Maybe<boolean>;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Creates an {@link OpenRouterPromptService}.
|
|
105
|
+
*
|
|
106
|
+
* @param config - The collections and cache configuration.
|
|
107
|
+
* @returns The service.
|
|
108
|
+
*/
|
|
109
|
+
export declare function openRouterPromptService(config: OpenRouterPromptServiceConfig): OpenRouterPromptService;
|