@clone-ai/prompt-prediction 0.7.0-bootstrap.0 → 0.7.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/CONTRIBUTING.md +52 -0
  3. package/LICENSE +21 -0
  4. package/README.md +119 -1
  5. package/SECURITY.md +9 -0
  6. package/dist/assistant-ui.d.ts +5 -0
  7. package/dist/assistant-ui.js +26 -0
  8. package/dist/clone-mode.d.ts +64 -0
  9. package/dist/clone-mode.js +156 -0
  10. package/dist/controller.d.ts +61 -0
  11. package/dist/controller.js +155 -0
  12. package/dist/deadline.d.ts +2 -0
  13. package/dist/deadline.js +25 -0
  14. package/dist/feedback.d.ts +50 -0
  15. package/dist/feedback.js +118 -0
  16. package/dist/generated/api-types.d.ts +1663 -0
  17. package/dist/generated/api-types.js +5 -0
  18. package/dist/http.d.ts +8 -0
  19. package/dist/http.js +35 -0
  20. package/dist/index.d.ts +9 -0
  21. package/dist/index.js +4 -0
  22. package/dist/react.d.ts +50 -0
  23. package/dist/react.js +147 -0
  24. package/dist/server.d.ts +33 -0
  25. package/dist/server.js +93 -0
  26. package/dist/transport.d.ts +15 -0
  27. package/dist/transport.js +42 -0
  28. package/dist/types.d.ts +8 -0
  29. package/dist/types.js +1 -0
  30. package/docs/agent-integration.md +122 -0
  31. package/docs/api.md +69 -0
  32. package/docs/billing.md +53 -0
  33. package/docs/browser-onboarding.md +69 -0
  34. package/docs/clone-mode.md +40 -0
  35. package/docs/context-mapping.md +28 -0
  36. package/docs/data-and-service.md +25 -0
  37. package/docs/developer-apps.md +87 -0
  38. package/docs/feedback.md +70 -0
  39. package/docs/pilot-validation.md +35 -0
  40. package/docs/releases.md +88 -0
  41. package/docs/reliability.md +33 -0
  42. package/docs/start.md +91 -0
  43. package/examples/react/composer-events.ts +3 -0
  44. package/examples/react/demo.tsx +195 -0
  45. package/examples/react/index.html +12 -0
  46. package/examples/react/mode-demo.tsx +95 -0
  47. package/examples/react/pilot-observations.ts +57 -0
  48. package/examples/react/vite.config.ts +123 -0
  49. package/openapi.json +2268 -0
  50. package/package.json +99 -4
  51. package/playwright.config.ts +13 -0
  52. package/release-manifest.json +63 -0
  53. package/scripts/create-example.mjs +31 -0
  54. package/scripts/pilot-metrics.mjs +91 -0
  55. package/tests/browser/clone-mode-options.tsx +38 -0
  56. package/tests/browser/clone-mode.spec.ts +68 -0
  57. package/tests/browser/composer.spec.ts +238 -0
@@ -0,0 +1,123 @@
1
+ import { defineConfig } from 'vite';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { CloneClient } from '../../src/server.js';
4
+ import { ClonePredictionError } from '../../src/transport.js';
5
+ import type { ConnectionFlow } from '../../src/server.js';
6
+ import { createPilotRecorder, createPredictionSources } from './pilot-observations.js';
7
+
8
+ const port = Number(process.env.CLONE_SDK_DEMO_PORT || 4317);
9
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) throw new Error('Invalid CLONE_SDK_DEMO_PORT');
10
+ const origin = `http://127.0.0.1:${port}`;
11
+
12
+ // Loopback-only integration example. Replace this explicit fixture identity and
13
+ // in-memory session with your application's authenticated session in production.
14
+ export default defineConfig({
15
+ root: fileURLToPath(new URL('.', import.meta.url)),
16
+ server: { host: '127.0.0.1', port, strictPort: true },
17
+ plugins: [{ name: 'clone-local-customer-backend', configureServer(server) {
18
+ if (process.env.CLONE_DEMO_BACKEND !== '1') return;
19
+ if (!process.env.CLONE_API_URL || !process.env.CLONE_APP_KEY) {
20
+ throw new Error('Connected demo requires server-only CLONE_API_URL and CLONE_APP_KEY');
21
+ }
22
+ const client = new CloneClient({ baseUrl: process.env.CLONE_API_URL, apiKey: process.env.CLONE_APP_KEY });
23
+ const user = 'demo-customer-user';
24
+ const faultsEnabled = process.env.CLONE_DEMO_FAULTS === '1';
25
+ const faults = new Set(['none', '503', '429', '402', 'network', 'malformed', 'timeout', 'latency']);
26
+ let fault = 'none';
27
+ const providerSource = process.env.CLONE_DEMO_PROVIDER_MODE === 'live' ? 'live' : 'fixture';
28
+ const predictionSources = createPredictionSources(providerSource);
29
+ const record = createPilotRecorder(process.env.CLONE_SDK_METRICS_FILE, providerSource, process.env.CLONE_SDK_PILOT_LABEL);
30
+ let flow: ConnectionFlow | null = null;
31
+ let connectionId = '';
32
+ server.middlewares.use(async (req, res, next) => {
33
+ const url = new URL(req.url ?? '/', origin);
34
+ if (!url.pathname.startsWith('/api/clone/') && url.pathname !== '/callback') return next();
35
+ res.setHeader('Cache-Control', 'no-store');
36
+ res.setHeader('Referrer-Policy', 'no-referrer');
37
+ const json = (status: number, body: unknown) => {
38
+ if (!res.destroyed) { res.writeHead(status, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(body)); }
39
+ };
40
+ try {
41
+ if (req.method === 'POST' && req.headers.origin !== origin) {
42
+ return json(403, { detail: { code: 'invalid_origin' } });
43
+ }
44
+ if (url.pathname === '/callback' && req.method === 'GET') {
45
+ if (!flow) return json(400, { detail: { code: 'missing_connect_session' } });
46
+ const connected = await client.exchange(flow, url.searchParams, user);
47
+ connectionId = connected.connection_id; flow = null;
48
+ res.writeHead(303, { Location: '/?connected=1' }); res.end(); return;
49
+ }
50
+ if (url.pathname === '/api/clone/state' && req.method === 'GET') {
51
+ return json(200, { connection_id: connectionId, faults_enabled: faultsEnabled });
52
+ }
53
+ if (url.pathname === '/api/clone/usage' && req.method === 'GET') return json(200, await client.usage());
54
+ if (req.method !== 'POST') return json(405, { detail: { code: 'method_not_allowed' } });
55
+ if (url.pathname === '/api/clone/connect') {
56
+ const started = await client.connect(user, origin + '/callback');
57
+ flow = started.flow;
58
+ return json(200, { authorizeUrl: started.authorizeUrl });
59
+ }
60
+ if (url.pathname === '/api/clone/disconnect') {
61
+ if (connectionId) await client.revoke(user, connectionId);
62
+ connectionId = ''; return json(200, { status: 'revoked' });
63
+ }
64
+ if (url.pathname === '/api/clone/clear-feedback') return json(200, await client.clearFeedback(user));
65
+ let raw = '';
66
+ for await (const chunk of req) {
67
+ raw += chunk; if (Buffer.byteLength(raw) > 300_000) return json(413, { detail: { code: 'body_too_large' } });
68
+ }
69
+ const body = JSON.parse(raw);
70
+ if (url.pathname === '/api/clone/test-fault' && faultsEnabled) {
71
+ if (!faults.has(body.fault)) return json(400, { detail: { code: 'invalid_test_fault' } });
72
+ fault = body.fault; return json(200, { fault });
73
+ }
74
+ if (url.pathname === '/api/clone/metrics') {
75
+ const source = body.kind === 'session' ? providerSource : predictionSources.get(body.request_id);
76
+ // Unattributed observations cannot be counted as live usage. Source
77
+ // comes from server-owned prediction admission, never a browser label.
78
+ if (source) record({ ...body, source });
79
+ return json(200, { status: 'recorded' });
80
+ }
81
+ if (url.pathname === '/api/clone/events') {
82
+ // Preserve the client event ID so a retry cannot create a second observation.
83
+ const { observed_at, session_id, ...event } = body;
84
+ try {
85
+ const result = await client.recordEvent(user, event);
86
+ const source = predictionSources.get(event.request_id);
87
+ if (source) record({ ...event, observed_at, session_id, source, delivery: 'recorded' });
88
+ return json(200, result);
89
+ } catch (error) {
90
+ const source = predictionSources.get(event.request_id);
91
+ if (source) record({ ...event, observed_at, session_id, source, delivery: 'failed' }); throw error;
92
+ }
93
+ }
94
+ // The server session owns optional personalization, never the browser body.
95
+ if ((body.connection_id ?? null) !== (connectionId || null)) return json(403, { detail: { code: 'connection_mismatch' } });
96
+ if (url.pathname === '/api/clone/predict') {
97
+ record({ kind: 'prediction', request_id: body.request_id, session_id: body.session_id,
98
+ source: predictionSources.register(body.request_id, fault !== 'none') });
99
+ if (fault === 'network') { res.destroy(); return; }
100
+ if (fault === 'timeout') {
101
+ res.writeHead(200, { 'Content-Type': 'application/json' }); res.write('{'); return;
102
+ }
103
+ if (fault === 'malformed') {
104
+ res.writeHead(200, { 'Content-Type': 'application/json' }); res.end('{invalid'); return;
105
+ }
106
+ if (['402', '429', '503'].includes(fault)) {
107
+ res.setHeader('Retry-After', '1');
108
+ return json(Number(fault), { detail: { code: fault === '429' ? 'rate_limited' : 'test_outage' } });
109
+ }
110
+ if (fault === 'latency') await new Promise(resolve => setTimeout(resolve, 5000));
111
+ if (res.destroyed) return;
112
+ const abort = new AbortController();
113
+ res.on('close', () => { if (!res.writableEnded) abort.abort(); });
114
+ return json(200, await client.predict(user, body, { signal: abort.signal }));
115
+ }
116
+ return json(404, { detail: { code: 'not_found' } });
117
+ } catch (error) {
118
+ return json(error instanceof ClonePredictionError ? error.status : 500,
119
+ { detail: { code: error instanceof ClonePredictionError ? error.code : 'example_backend_error' } });
120
+ }
121
+ });
122
+ } }],
123
+ });