@ossy/platform 3.15.0 → 3.17.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/package.json +9 -8
- package/src/audit/audit-helpers.js +60 -6
- package/src/audit/audit-helpers.spec.js +42 -0
- package/src/audit/task-run-list.aggregate.js +3 -0
- package/src/audit/task-run-service.js +10 -2
- package/src/audit/task-run.aggregate.js +2 -0
- package/src/file.schema.js +0 -4
- package/src/metering/assert-llm-usage-budget.js +83 -0
- package/src/metering/assert-llm-usage-budget.spec.js +129 -0
- package/src/metering/get-workspace-llm-usage.action.js +5 -0
- package/src/metering/get-workspace-llm-usage.task.js +31 -0
- package/src/metering/get-workspace-llm-usage.task.spec.js +74 -0
- package/src/metering/get-workspace-task-metering.action.js +5 -0
- package/src/metering/get-workspace-task-metering.task.js +30 -0
- package/src/metering/get-workspace-task-metering.task.spec.js +43 -0
- package/src/metering/index.js +11 -0
- package/src/metering/llm-usage.aggregate.js +127 -0
- package/src/metering/llm-usage.aggregate.spec.js +95 -0
- package/src/metering/metering-service.js +13 -0
- package/src/metering/metering-service.spec.js +62 -0
- package/src/metering/task-metering.aggregate.js +108 -0
- package/src/metering/task-metering.aggregate.spec.js +71 -0
- package/src/metering/usage-month.js +7 -0
- package/src/runtime.js +2 -0
- package/src/server.js +2 -0
- package/src/ssr/detect-mobile-device.js +39 -0
- package/src/ssr/detect-mobile-device.spec.js +50 -0
- package/src/storage/storage-keys.js +24 -0
- package/src/storage/storage-keys.spec.js +12 -1
- package/src/tasks/in-process-sdk.js +1 -49
- package/src/tasks/in-process-sdk.spec.js +1 -39
- package/src/tasks/task-service.js +270 -41
- package/src/tasks/task-service.spec.js +108 -0
- package/src/text-completion.schema.js +37 -0
|
@@ -63,6 +63,73 @@ describe('TaskService triggers', () => {
|
|
|
63
63
|
)).toBe(true)
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
+
it('AND-narrows events with instance watch locations and MIME', () => {
|
|
67
|
+
const event = {
|
|
68
|
+
type: '@ossy/platform/schema/file',
|
|
69
|
+
event: 'Created',
|
|
70
|
+
resourceId: 'f1',
|
|
71
|
+
payload: {
|
|
72
|
+
belongsTo: 'ws1',
|
|
73
|
+
location: '/incoming/photos/a.jpg',
|
|
74
|
+
content: { ContentType: 'image/jpeg' },
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
expect(TaskService._matchesEventTrigger({
|
|
79
|
+
kind: 'on_event',
|
|
80
|
+
type: '@ossy/platform/schema/file',
|
|
81
|
+
event: 'Created',
|
|
82
|
+
location: { startsWith: '/incoming/' },
|
|
83
|
+
match: { 'content.ContentType': 'image/*' },
|
|
84
|
+
}, event)).toBe(true)
|
|
85
|
+
|
|
86
|
+
expect(TaskService._matchesEventTrigger({
|
|
87
|
+
kind: 'on_event',
|
|
88
|
+
type: '@ossy/platform/schema/file',
|
|
89
|
+
event: 'Created',
|
|
90
|
+
location: { startsWith: '/other/' },
|
|
91
|
+
}, event)).toBe(false)
|
|
92
|
+
|
|
93
|
+
expect(TaskService._matchesEventTrigger({
|
|
94
|
+
kind: 'on_event',
|
|
95
|
+
type: '@ossy/platform/schema/file',
|
|
96
|
+
event: 'Created',
|
|
97
|
+
match: { 'content.ContentType': 'video/*' },
|
|
98
|
+
}, event)).toBe(false)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('treats configurable tasks without instance triggers as unarmed', async () => {
|
|
102
|
+
TaskService.setInstanceTriggersResolver(async () => [])
|
|
103
|
+
const result = await TaskService._eventTriggersForDispatch(
|
|
104
|
+
{ id: 't1', configurable: true, triggers: [{ type: 'x', event: 'Created' }] },
|
|
105
|
+
'ws1',
|
|
106
|
+
)
|
|
107
|
+
expect(result.armed).toBe(false)
|
|
108
|
+
expect(result.triggers).toEqual([])
|
|
109
|
+
TaskService.setInstanceTriggersResolver(null)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('uses instance triggers for configurable tasks', async () => {
|
|
113
|
+
const instanceTriggers = [{
|
|
114
|
+
kind: 'on_event',
|
|
115
|
+
type: '@ossy/platform/schema/file',
|
|
116
|
+
event: 'Created',
|
|
117
|
+
location: { startsWith: '/incoming/' },
|
|
118
|
+
}]
|
|
119
|
+
TaskService.setInstanceTriggersResolver(async () => instanceTriggers)
|
|
120
|
+
const result = await TaskService._eventTriggersForDispatch(
|
|
121
|
+
{
|
|
122
|
+
id: 't1',
|
|
123
|
+
configurable: true,
|
|
124
|
+
triggers: [{ type: '@ossy/platform/schema/file', event: 'Created' }],
|
|
125
|
+
},
|
|
126
|
+
'ws1',
|
|
127
|
+
)
|
|
128
|
+
expect(result.armed).toBe(true)
|
|
129
|
+
expect(result.triggers).toEqual(instanceTriggers)
|
|
130
|
+
TaskService.setInstanceTriggersResolver(null)
|
|
131
|
+
})
|
|
132
|
+
|
|
66
133
|
it('skips primary derived task in on_action dispatch', () => {
|
|
67
134
|
expect(isPrimaryTaskForAction('@ossy/booking/actions/create', '@ossy/booking/tasks/create')).toBe(true)
|
|
68
135
|
expect(isPrimaryTaskForAction('@ossy/booking/actions/create', '@ossy/booking/tasks/send-reminder')).toBe(false)
|
|
@@ -120,6 +187,11 @@ describe('TaskRunList projection', () => {
|
|
|
120
187
|
moduleId: 'media-tasks',
|
|
121
188
|
trigger: 'on_event',
|
|
122
189
|
belongsTo: 'ws1',
|
|
190
|
+
triggeredBy: {
|
|
191
|
+
resourceId: 'file1',
|
|
192
|
+
type: '@ossy/platform/schema/file',
|
|
193
|
+
event: 'Created',
|
|
194
|
+
},
|
|
123
195
|
},
|
|
124
196
|
}
|
|
125
197
|
|
|
@@ -127,6 +199,11 @@ describe('TaskRunList projection', () => {
|
|
|
127
199
|
state = TaskRunListProjection.Apply(started, state)
|
|
128
200
|
expect(state.runs).toHaveLength(1)
|
|
129
201
|
expect(state.runs[0].status).toBe('in progress')
|
|
202
|
+
expect(state.runs[0].triggeredBy).toEqual({
|
|
203
|
+
resourceId: 'file1',
|
|
204
|
+
type: '@ossy/platform/schema/file',
|
|
205
|
+
event: 'Created',
|
|
206
|
+
})
|
|
130
207
|
|
|
131
208
|
state = TaskRunListProjection.Apply({
|
|
132
209
|
resourceId: 'run1',
|
|
@@ -145,6 +222,37 @@ describe('TaskRunList projection', () => {
|
|
|
145
222
|
expect(state.runs[0].durationMs).toBe(1000)
|
|
146
223
|
})
|
|
147
224
|
|
|
225
|
+
it('keeps resolved inputs on the listed run', () => {
|
|
226
|
+
let state = TaskRunListProjection.initialState('ws1')
|
|
227
|
+
state = TaskRunListProjection.Apply({
|
|
228
|
+
resourceId: 'run-inputs',
|
|
229
|
+
type: '@ossy/platform/schema/task-run',
|
|
230
|
+
event: 'Started',
|
|
231
|
+
created: 1000,
|
|
232
|
+
payload: {
|
|
233
|
+
taskId: '@ossy/media-tasks/tasks/visual-content-descriptors',
|
|
234
|
+
moduleId: 'media-tasks',
|
|
235
|
+
trigger: 'on_event',
|
|
236
|
+
belongsTo: 'ws1',
|
|
237
|
+
},
|
|
238
|
+
}, state)
|
|
239
|
+
state = TaskRunListProjection.Apply({
|
|
240
|
+
resourceId: 'run-inputs',
|
|
241
|
+
event: 'Completed',
|
|
242
|
+
created: 2000,
|
|
243
|
+
payload: {
|
|
244
|
+
durationMs: 40,
|
|
245
|
+
resultSummary: { title: 'A cat' },
|
|
246
|
+
inputs: { model: 'google/gemini-2.5-flash-lite' },
|
|
247
|
+
belongsTo: 'ws1',
|
|
248
|
+
taskId: '@ossy/media-tasks/tasks/visual-content-descriptors',
|
|
249
|
+
moduleId: 'media-tasks',
|
|
250
|
+
},
|
|
251
|
+
}, state)
|
|
252
|
+
|
|
253
|
+
expect(state.runs[0].inputs).toEqual({ model: 'google/gemini-2.5-flash-lite' })
|
|
254
|
+
})
|
|
255
|
+
|
|
148
256
|
it('scopes Completed/Failed from payload.belongsTo (changestream dispatch)', () => {
|
|
149
257
|
expect(TaskRunListProjection.scopeFromEvent({
|
|
150
258
|
payload: { durationMs: 10 },
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { TASK_OUTPUT_PROVENANCE_FIELDS } from '@ossy/schema'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Platform schema for freeform text/Markdown LLM completion artifacts.
|
|
5
|
+
*
|
|
6
|
+
* Any task that produces unstructured prose (a chat completion, a
|
|
7
|
+
* generated summary, …) placed as a document should declare this schema
|
|
8
|
+
* as its output `schemaId` instead of inventing a feature-specific one.
|
|
9
|
+
* Structured JSON results (e.g. `{ title, tags, alt }`) still get their
|
|
10
|
+
* own feature schema — this one is only for the "freeform text/md" shape.
|
|
11
|
+
*
|
|
12
|
+
* See `docs/concepts/TaskOutput.md`.
|
|
13
|
+
*/
|
|
14
|
+
export default {
|
|
15
|
+
id: '@ossy/platform/schema/text-completion',
|
|
16
|
+
name: 'Text completion',
|
|
17
|
+
categoryName: 'Platform',
|
|
18
|
+
icon: 'comment',
|
|
19
|
+
fields: [
|
|
20
|
+
{
|
|
21
|
+
name: 'text',
|
|
22
|
+
type: 'textarea',
|
|
23
|
+
description: 'Model completion (freeform text/Markdown)',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'model',
|
|
27
|
+
type: 'text',
|
|
28
|
+
description: 'Model slug that produced the completion',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'prompt',
|
|
32
|
+
type: 'textarea',
|
|
33
|
+
description: 'Prompt that was sent, before resource context',
|
|
34
|
+
},
|
|
35
|
+
...TASK_OUTPUT_PROVENANCE_FIELDS,
|
|
36
|
+
],
|
|
37
|
+
}
|