@gotcos/glasses-server 6.28.0 → 6.29.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/CHANGELOG.md +30 -0
- package/package.json +1 -1
- package/server/index.ts +45 -0
- package/server/lib/fork-thread.ts +957 -0
- package/server/lib/thread-attach-capability.ts +200 -0
- package/server/routes/agent-session-bindings.ts +475 -1
- package/server/routes/health.ts +24 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// Does this server offer Continue at all, and for what?
|
|
2
|
+
//
|
|
3
|
+
// The Continue/Fork affordance is a UI decision that has to be made BEFORE any
|
|
4
|
+
// thread is named, so COS Control and the phone cannot discover it the way they
|
|
5
|
+
// discover everything else about a thread — by asking
|
|
6
|
+
// /api/agent-sessions/:provider/:threadId/attachability. When the feature is off
|
|
7
|
+
// the two write routes are not registered at all, so a client that reaches for
|
|
8
|
+
// them gets a bare 404 with no reason and no copy, which is indistinguishable
|
|
9
|
+
// from a typo, a proxy, or a dead server. This module is what a client reads
|
|
10
|
+
// instead, once, up front.
|
|
11
|
+
//
|
|
12
|
+
// ---------------------------------------------------------------- the rule
|
|
13
|
+
//
|
|
14
|
+
// ABSENT MEANS DISABLED. NEVER ENABLED.
|
|
15
|
+
//
|
|
16
|
+
// These fields did not exist before this build. Every client older than it will
|
|
17
|
+
// read `undefined` from all three, and `undefined` MUST resolve to off — the same
|
|
18
|
+
// fail-closed posture as the rest of Continue Original Agent Thread, for the same
|
|
19
|
+
// reason: a client that guesses "the field is missing, so probably fine" offers a
|
|
20
|
+
// Continue button that writes into a real human's conversation, and the cost of
|
|
21
|
+
// guessing the other way is one Fork.
|
|
22
|
+
//
|
|
23
|
+
// That rule cannot be enforced by the wire format, because absence is exactly what
|
|
24
|
+
// an old client sees and an old client cannot be changed. So it is enforced by
|
|
25
|
+
// `readThreadAttachCapability`, which is the ONLY sanctioned way to interpret this
|
|
26
|
+
// surface, and by the test that feeds it a genuine older-shaped /api/health body
|
|
27
|
+
// and requires all three answers to come back off.
|
|
28
|
+
//
|
|
29
|
+
// `supported` exists solely to make the two OFFs distinguishable. Without it,
|
|
30
|
+
// "this server has never heard of Continue" and "this server can Continue but the
|
|
31
|
+
// user has not switched it on" are the same empty answer, and a client cannot tell
|
|
32
|
+
// a user to flip a setting that may not exist. It is a compile-time constant: this
|
|
33
|
+
// build has the code. Whether the routes are reachable is `enabled`.
|
|
34
|
+
//
|
|
35
|
+
// ------------------------------------------------------------ anti-drift
|
|
36
|
+
//
|
|
37
|
+
// `enabled` reads `threadAttachEnabled()` — the SAME function
|
|
38
|
+
// routes/agent-session-bindings.ts calls to decide whether to register the two
|
|
39
|
+
// POST routes. Not a second copy of `process.env.COS_THREAD_ATTACH_ENABLED === '1'`.
|
|
40
|
+
// A copy is how this surface comes to advertise a write path that 404s, and the
|
|
41
|
+
// repo already carries that lesson one import above the call site in
|
|
42
|
+
// routes/health.ts: MEDIA_CHUNKED_UPLOAD_ENABLED is imported from the route that
|
|
43
|
+
// owns the endpoints "so it cannot drift from whether they are actually
|
|
44
|
+
// registered."
|
|
45
|
+
//
|
|
46
|
+
// `providers` is sourced from BINDABLE_PROVIDERS for the same reason. Cursor must
|
|
47
|
+
// not appear (plan 2.5 makes it Fork-only), and the way to guarantee that is to
|
|
48
|
+
// read the list that `isBindableProvider` — the check the attach route itself
|
|
49
|
+
// applies — is built from, rather than restating two names here and hoping.
|
|
50
|
+
//
|
|
51
|
+
// ------------------------------------------------- why providers empties out
|
|
52
|
+
//
|
|
53
|
+
// When the gate is off, `providers` is `[]` rather than the list this build could
|
|
54
|
+
// drive if it were on. A client that reads only `threadAttachProviders` and never
|
|
55
|
+
// looks at `threadAttachEnabled` is then still correct. Every field independently
|
|
56
|
+
// resolves to "no" when the answer is no; there is no combination of fields a
|
|
57
|
+
// careless reader can pick that yields a Continue button pointing at an
|
|
58
|
+
// unregistered route.
|
|
59
|
+
//
|
|
60
|
+
// The cost is that a disabled server cannot tell an operator WHICH providers it
|
|
61
|
+
// would support. That is a settings-copy problem, and `supported: true` is enough
|
|
62
|
+
// to say "this build can do it, turn it on."
|
|
63
|
+
|
|
64
|
+
import {
|
|
65
|
+
BINDABLE_PROVIDERS,
|
|
66
|
+
isBindableProvider,
|
|
67
|
+
type BindableProvider,
|
|
68
|
+
} from './agent-session-binding-store.js'
|
|
69
|
+
import { threadAttachEnabled } from '../routes/agent-session-bindings.js'
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* This build has the feature compiled in.
|
|
73
|
+
*
|
|
74
|
+
* A constant, not a probe. There is no configuration under which the code is
|
|
75
|
+
* absent from a build that contains this file; the reachable/unreachable question
|
|
76
|
+
* is `enabled`. It is published so that `undefined` (an older server) and `false`
|
|
77
|
+
* (this build, switched off) are different answers on the wire.
|
|
78
|
+
*/
|
|
79
|
+
export const THREAD_ATTACH_SUPPORTED = true
|
|
80
|
+
|
|
81
|
+
export interface ThreadAttachCapability {
|
|
82
|
+
/** The build knows what Continue is. False only when read off an older payload. */
|
|
83
|
+
supported: boolean
|
|
84
|
+
/** The write routes are registered and a Continue may be attempted. */
|
|
85
|
+
enabled: boolean
|
|
86
|
+
/**
|
|
87
|
+
* Providers that can be CONTINUED, not merely browsed or forked.
|
|
88
|
+
*
|
|
89
|
+
* Empty whenever `enabled` is false. Cursor is never a member: it is Fork-only.
|
|
90
|
+
*/
|
|
91
|
+
providers: BindableProvider[]
|
|
92
|
+
/**
|
|
93
|
+
* Fork is available. NOT gated by `enabled`.
|
|
94
|
+
*
|
|
95
|
+
* Fork creates a new thread and leaves the source byte-identical, so the flag
|
|
96
|
+
* that protects an existing conversation does not apply. Gating it produced an
|
|
97
|
+
* incoherent default: every refusal recommends Fork while the route 404s.
|
|
98
|
+
*/
|
|
99
|
+
forkSupported: boolean
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The capability as this server should publish it.
|
|
104
|
+
*
|
|
105
|
+
* @param gate Tests only. Production takes the default, which is the same
|
|
106
|
+
* function that decides whether the write routes exist. Passing anything else
|
|
107
|
+
* from production code re-opens the drift this module was written to close.
|
|
108
|
+
*/
|
|
109
|
+
export function threadAttachCapability(
|
|
110
|
+
gate: () => boolean = threadAttachEnabled,
|
|
111
|
+
): ThreadAttachCapability {
|
|
112
|
+
let enabled = false
|
|
113
|
+
try {
|
|
114
|
+
// Anything other than an exact `true` is off. A gate that answers "maybe" is
|
|
115
|
+
// answering no, and a gate that throws has not answered at all.
|
|
116
|
+
enabled = gate() === true
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error(
|
|
119
|
+
`[thread-attach-capability] gate threw: ${error instanceof Error ? error.message : error}`,
|
|
120
|
+
)
|
|
121
|
+
enabled = false
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
supported: THREAD_ATTACH_SUPPORTED,
|
|
125
|
+
enabled,
|
|
126
|
+
providers: enabled ? [...BINDABLE_PROVIDERS] : [],
|
|
127
|
+
// NOT gated by `enabled`: fork creates a new thread and leaves the source
|
|
128
|
+
// byte-identical, so the flag protecting an existing conversation does not
|
|
129
|
+
// apply. Gating it made every refusal recommend a route that 404s.
|
|
130
|
+
forkSupported: THREAD_ATTACH_SUPPORTED,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The three keys as they appear on /api/health and /api/models. */
|
|
135
|
+
export interface ThreadAttachHealthFields {
|
|
136
|
+
threadAttachSupported: boolean
|
|
137
|
+
threadAttachEnabled: boolean
|
|
138
|
+
/** Fork is available whenever the build supports it, gate or no gate. */
|
|
139
|
+
threadForkSupported: boolean
|
|
140
|
+
threadAttachProviders: BindableProvider[]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Flatten the capability onto the health payload's key names. */
|
|
144
|
+
export function threadAttachHealthFields(
|
|
145
|
+
capability: ThreadAttachCapability,
|
|
146
|
+
): ThreadAttachHealthFields {
|
|
147
|
+
return {
|
|
148
|
+
threadAttachSupported: capability.supported,
|
|
149
|
+
threadAttachEnabled: capability.enabled,
|
|
150
|
+
threadAttachProviders: capability.providers,
|
|
151
|
+
threadForkSupported: capability.forkSupported,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Interpret a health payload from ANY server, including one that predates these
|
|
157
|
+
* fields entirely.
|
|
158
|
+
*
|
|
159
|
+
* The whole point of this function is the defaults, so read them as the contract:
|
|
160
|
+
*
|
|
161
|
+
* - a payload that is not an object at all -> everything off
|
|
162
|
+
* - `threadAttachSupported` anything but exactly `true` -> not supported, and
|
|
163
|
+
* therefore not enabled, whatever the other two fields say
|
|
164
|
+
* - `threadAttachEnabled` anything but exactly `true` -> off, so `'true'`, `1`
|
|
165
|
+
* and `'yes'` from a hand-rolled or proxied payload all fail closed
|
|
166
|
+
* - `providers` dropped entirely unless enabled, and filtered to names THIS
|
|
167
|
+
* build can actually drive, so a newer server naming a fourth provider does
|
|
168
|
+
* not make an older client offer a Continue it cannot perform
|
|
169
|
+
*
|
|
170
|
+
* A payload that contradicts itself — enabled without supported, or providers
|
|
171
|
+
* while disabled — is a defect somewhere, and a defect resolves to refuse.
|
|
172
|
+
*/
|
|
173
|
+
export function readThreadAttachCapability(payload: unknown): ThreadAttachCapability {
|
|
174
|
+
const row =
|
|
175
|
+
payload && typeof payload === 'object' && !Array.isArray(payload)
|
|
176
|
+
? (payload as Record<string, unknown>)
|
|
177
|
+
: null
|
|
178
|
+
|
|
179
|
+
const supported = row?.threadAttachSupported === true
|
|
180
|
+
const enabled = supported && row?.threadAttachEnabled === true
|
|
181
|
+
|
|
182
|
+
const providers: BindableProvider[] = []
|
|
183
|
+
const raw = row?.threadAttachProviders
|
|
184
|
+
if (enabled && Array.isArray(raw)) {
|
|
185
|
+
for (const entry of raw) {
|
|
186
|
+
// `isBindableProvider` is the attach route's own check. Anything it rejects
|
|
187
|
+
// is a provider this build has no code path for, so offering it would
|
|
188
|
+
// produce a Continue that cannot be honoured.
|
|
189
|
+
if (!isBindableProvider(entry)) continue
|
|
190
|
+
if (providers.includes(entry)) continue
|
|
191
|
+
providers.push(entry)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Read off a payload from another server. An older build has no fork route, and
|
|
196
|
+
// its payload carries no such field, so absent must read as NOT supported — the
|
|
197
|
+
// same fail-closed rule the rest of this module runs on.
|
|
198
|
+
const forkSupported = row?.threadForkSupported === true
|
|
199
|
+
return { supported, enabled, providers, forkSupported }
|
|
200
|
+
}
|