@oar-sh/oar 0.9.24 → 0.9.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.github/extensions/web-relay/skills/reasoning-stream.mjs +3 -1
  2. package/CHANGELOG.md +142 -0
  3. package/README.md +12 -11
  4. package/package.json +3 -1
  5. package/server/claude-worker/claude-remote-relay-tool.mjs +15 -0
  6. package/server/claude-worker/sdk-message-normalizer.mjs +29 -1
  7. package/server/copilot-worker/copilot-sdk-session-process.mjs +11 -9
  8. package/server/cursor-worker/sdk-message-normalizer.mjs +13 -0
  9. package/server/db-schema.mjs +19 -0
  10. package/server/grok-worker/sdk-message-normalizer.mjs +3 -0
  11. package/server/migrations/0039-media-index.mjs +167 -0
  12. package/server/migrations/0040-image-session-defaults.mjs +93 -0
  13. package/server/public/app/api-client.js +58 -0
  14. package/server/public/app/ask-user-view.js +2 -1
  15. package/server/public/app/attachments-view.js +66 -9
  16. package/server/public/app/bootstrap.js +85 -25
  17. package/server/public/app/chat-actions-menu-state.mjs +1 -0
  18. package/server/public/app/context-media-tab.js +143 -0
  19. package/server/public/app/conversation-view.js +90 -5
  20. package/server/public/app/image-composer-ui.js +518 -0
  21. package/server/public/app/image-turn-placeholder.mjs +233 -0
  22. package/server/public/app/journal-view.js +12 -2
  23. package/server/public/app/media-gallery-model.mjs +202 -0
  24. package/server/public/app/media-gallery-ui.js +524 -0
  25. package/server/public/app/media-view.js +79 -0
  26. package/server/public/app/openai-image-composer.mjs +492 -0
  27. package/server/public/app/openai-image-cost-ui.js +426 -0
  28. package/server/public/app/openai-image-spend.mjs +443 -0
  29. package/server/public/app/plan-usage-view.mjs +38 -6
  30. package/server/public/app/relay-error-ctas.mjs +5 -0
  31. package/server/public/app/session-history-ui.js +2 -0
  32. package/server/public/app/settings-modal.js +4 -0
  33. package/server/public/app/socket-handlers.js +26 -0
  34. package/server/public/app/tab-strip-fade.mjs +1 -1
  35. package/server/public/app/viewer-gallery.mjs +23 -1
  36. package/server/public/index.html +383 -7
  37. package/server/relay-tools.md +1 -0
  38. package/server/repositories/image-conversation-repository.mjs +69 -0
  39. package/server/repositories/media-repository.mjs +305 -0
  40. package/server/routes/media-routes.mjs +361 -0
  41. package/server/routes/messages-routes.mjs +1410 -360
  42. package/server/routes/sessions-routes.mjs +361 -14
  43. package/server/server-runtime.mjs +19 -0
  44. package/server/services/database-file.mjs +6 -4
  45. package/server/services/generated-image-cleanup-service.mjs +2 -1
  46. package/server/services/generated-image-files.mjs +110 -0
  47. package/server/services/image-input-paths.mjs +389 -0
  48. package/server/services/image-operation-service.mjs +6 -2
  49. package/server/services/media-extract.mjs +653 -0
  50. package/server/services/media-index-service.mjs +221 -0
  51. package/server/services/openai-image-preview-turns.mjs +330 -0
  52. package/server/services/openai-image-spend-service.mjs +516 -0
  53. package/server/services/openai-image-stream.mjs +170 -0
  54. package/server/services/plan-usage-openai-image.mjs +148 -0
  55. package/server/services/remote-relay-caller-context.mjs +6 -0
  56. package/server/services/remote-relay-dispatcher.mjs +386 -22
  57. package/server/services/remote-relay-registry-service.mjs +3 -1
  58. package/server/services/token-usage-service.mjs +15 -0
  59. package/shared/image-header.mjs +90 -0
  60. package/shared/openai-image-options.mjs +299 -0
  61. package/shared/openai-image-presets.mjs +175 -0
  62. package/shared/openai-image-previews.mjs +40 -0
  63. package/shared/openai-image-prices.mjs +291 -0
  64. package/shared/openai-image-spend.mjs +90 -0
  65. package/shared/remote-relay-contract.mjs +126 -4
  66. package/shared/worker-runtime/direct-openai-image.mjs +96 -50
@@ -0,0 +1,148 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * OpenAI images on the Check Usage modal (provider `openai`, the booking's
5
+ * provider, so the Tokens sections of plan-usage-tokens.mjs land on it).
6
+ *
7
+ * The figures are the spend state of shared/openai-image-spend.mjs: today
8
+ * and this month in the relay's local days, booked by OAR from its price
9
+ * table, estimates and the stand-in price of an unknown model included (they
10
+ * count toward the cap on purpose). The card shows the daily cap as a meter,
11
+ * the warning share, the owner's "Allow for today", and how much of today is
12
+ * estimated. The raw state rides along as `imageSpend` for the browser's cap
13
+ * bar.
14
+ */
15
+
16
+ import {
17
+ SOURCE_WORKER,
18
+ STATUS_OK,
19
+ STATUS_UNAVAILABLE,
20
+ buildMeter,
21
+ buildProviderCard,
22
+ roundCurrency,
23
+ toFiniteNumber,
24
+ } from './plan-usage-contract.mjs';
25
+ import {
26
+ OPENAI_IMAGE_BOOKING_PROVIDER,
27
+ OPENAI_IMAGE_CAP_WARN_RATIO,
28
+ formatOpenAIImageUsd,
29
+ } from '../../shared/openai-image-spend.mjs';
30
+
31
+ export const OPENAI_IMAGE_CARD_LABEL = 'OpenAI images';
32
+
33
+ const OPENAI_USAGE_URL = 'https://platform.openai.com/usage';
34
+
35
+ const usd = (value) => Math.max(0, toFiniteNumber(value) ?? 0);
36
+
37
+ /**
38
+ * The card, or null when there is nothing to show: OpenAI is off and nothing
39
+ * was booked this month. `state` is the spend state; `error` replaces it
40
+ * when it could not be read.
41
+ */
42
+ export function buildOpenAIImagePlanCard({ state = null, error = null, configured = false } = {}) {
43
+ const links = [{ label: 'OpenAI usage', url: OPENAI_USAGE_URL }];
44
+ if (!state) {
45
+ if (!configured) return null;
46
+ return buildProviderCard({
47
+ provider: OPENAI_IMAGE_BOOKING_PROVIDER,
48
+ label: OPENAI_IMAGE_CARD_LABEL,
49
+ status: STATUS_UNAVAILABLE,
50
+ message: `This relay's OpenAI image spend could not be read${error ? `: ${error}` : ''}.`,
51
+ source: SOURCE_WORKER,
52
+ links,
53
+ });
54
+ }
55
+ const spent = usd(state.spentUsd);
56
+ const reserved = usd(state.reservedUsd);
57
+ const month = usd(state.monthUsd);
58
+ if (!configured && month <= 0 && spent <= 0 && reserved <= 0) return null;
59
+
60
+ const capUsd = toFiniteNumber(state.capUsd);
61
+ const hasCap = capUsd !== null && capUsd > 0;
62
+ const used = roundCurrency(spent + reserved);
63
+ const allowed = state.allowedToday === true;
64
+ const percent = hasCap ? Math.round((used / capUsd) * 100) : null;
65
+
66
+ const messages = [];
67
+ if (hasCap && state.reached === true) {
68
+ messages.push(allowed
69
+ ? `Today's image spend has reached the daily cap of ${formatOpenAIImageUsd(capUsd)}; image turns run anyway, because the owner allowed them for today.`
70
+ : `Today's image spend has reached the daily cap of ${formatOpenAIImageUsd(capUsd)}: new image turns are refused until tomorrow (relay time), or until the owner allows more for today (Settings → Providers → OpenAI).`);
71
+ } else if (hasCap && state.warning === true) {
72
+ messages.push(`Today's image spend is at ${percent} % of the daily cap of ${formatOpenAIImageUsd(capUsd)}.`);
73
+ }
74
+ if (!hasCap) messages.push('No daily cap is set for OpenAI images on this relay.');
75
+
76
+ const meters = [];
77
+ if (hasCap) {
78
+ meters.push(buildMeter({
79
+ id: 'openai-images-daily-cap',
80
+ label: 'Daily cap',
81
+ unit: 'usd',
82
+ used,
83
+ allowance: capUsd,
84
+ remaining: roundCurrency(Math.max(0, capUsd - used)),
85
+ estimated: true,
86
+ severity: state.reached === true ? 'reached' : (state.warning === true ? 'warning' : null),
87
+ note: `This relay's image spend today (${state.day || 'relay time'}), images in progress included; a warning from ${Math.round(OPENAI_IMAGE_CAP_WARN_RATIO * 100)} %, and at the cap new image turns are refused${allowed ? ' (allowed for today by the owner)' : ''}. It starts over at midnight, relay time.`,
88
+ }));
89
+ }
90
+
91
+ const estimated = usd(state.estimatedUsd);
92
+ const unknown = usd(state.unknownPriceUsd);
93
+ const notes = [
94
+ {
95
+ id: 'openai-images-today',
96
+ label: 'Today',
97
+ value: formatOpenAIImageUsd(used),
98
+ hint: [
99
+ reserved > 0 ? `${formatOpenAIImageUsd(reserved)} in progress` : '',
100
+ estimated > 0 ? `${formatOpenAIImageUsd(estimated)} estimated` : '',
101
+ unknown > 0 ? `${formatOpenAIImageUsd(unknown)} at an unknown model's stand-in price` : '',
102
+ ].filter(Boolean).join(' · ') || null,
103
+ text: `OpenAI image spend today: ${formatOpenAIImageUsd(used)}${reserved > 0 ? `, of it ${formatOpenAIImageUsd(reserved)} in progress` : ''}.`,
104
+ },
105
+ {
106
+ id: 'openai-images-month',
107
+ label: 'This month',
108
+ value: formatOpenAIImageUsd(month),
109
+ text: `OpenAI image spend this month: ${formatOpenAIImageUsd(month)}.`,
110
+ },
111
+ {
112
+ id: 'openai-images-override',
113
+ label: 'Allow for today',
114
+ value: allowed ? 'on' : 'off',
115
+ hint: allowed && state.allowedAt ? `since ${state.allowedAt}` : null,
116
+ text: allowed
117
+ ? 'The owner allowed image turns past the cap for today; it ends at midnight, relay time.'
118
+ : 'Past the cap, image turns are refused unless the owner allows them for today.',
119
+ },
120
+ ];
121
+ const conversation = state.conversation && typeof state.conversation === 'object' ? state.conversation : null;
122
+ if (conversation) {
123
+ const images = Math.max(0, Math.trunc(toFiniteNumber(conversation.images) ?? 0));
124
+ notes.push({
125
+ id: 'openai-images-this-chat',
126
+ label: 'This chat',
127
+ value: formatOpenAIImageUsd(usd(conversation.costUsd)),
128
+ hint: `${images} ${images === 1 ? 'image' : 'images'}`,
129
+ text: `OpenAI images in this chat: ${formatOpenAIImageUsd(usd(conversation.costUsd))} (${images} ${images === 1 ? 'image' : 'images'}).`,
130
+ });
131
+ }
132
+ notes.push({
133
+ id: 'openai-images-estimate',
134
+ text: 'Priced by OAR from its own table of OpenAI image prices, for the image turns of this relay; a try that may have been billed without a usage report counts at its estimate. OpenAI\'s usage page has every request on the key.',
135
+ });
136
+
137
+ const card = buildProviderCard({
138
+ provider: OPENAI_IMAGE_BOOKING_PROVIDER,
139
+ label: OPENAI_IMAGE_CARD_LABEL,
140
+ status: STATUS_OK,
141
+ message: messages.join(' ') || null,
142
+ source: SOURCE_WORKER,
143
+ meters: meters.filter(Boolean),
144
+ notes,
145
+ links,
146
+ });
147
+ return card ? { ...card, imageSpend: state } : null;
148
+ }
@@ -153,6 +153,12 @@ function describeSettingsChange(args) {
153
153
  if (toText(args.thinking_display)) parts.push(`Thinking text: ${toText(args.thinking_display) === 'omitted' ? 'hidden' : 'visible'}`);
154
154
  if (toText(args.cwd)) parts.push(`Folder: ${excerpt(args.cwd)} (its CLI restarts there)`);
155
155
  if (typeof args.planOnly === 'boolean') parts.push(`planOnly: ${args.planOnly}`);
156
+ if (args.image && typeof args.image === 'object' && !Array.isArray(args.image)) {
157
+ const options = Object.entries(args.image)
158
+ .filter(([, value]) => toText(value))
159
+ .map(([name, value]) => `${name.replace(/_/g, ' ')} ${excerpt(value)}`);
160
+ if (options.length) parts.push(`Image defaults: ${options.join(', ')}`);
161
+ }
156
162
  return parts.join(' · ');
157
163
  }
158
164