@finalai/final-agent-provider 0.6.4 → 0.8.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/HOSTING.md +4 -4
- package/README.md +113 -2
- package/dist/modules/authoring-final-channel.mjs +2 -2
- package/dist/modules/authoring-final-channel.mjs.map +2 -2
- package/dist/modules/channel-factories.d.mts +5 -7
- package/dist/modules/channel-factories.mjs +4 -50
- package/dist/modules/channel-factories.mjs.map +2 -2
- package/dist/modules/channels/channel-utils.d.mts +132 -0
- package/dist/modules/channels/channel-utils.mjs +445 -0
- package/dist/modules/channels/channel-utils.mjs.map +7 -0
- package/dist/modules/channels/delivery.d.mts +5 -5
- package/dist/modules/channels/delivery.mjs.map +1 -1
- package/dist/modules/channels/direct-messaging.d.mts +2 -2
- package/dist/modules/channels/direct-messaging.mjs.map +2 -2
- package/dist/modules/channels/discord-dispatch.d.mts +17 -0
- package/dist/modules/channels/discord-dispatch.mjs +139 -0
- package/dist/modules/channels/discord-dispatch.mjs.map +7 -0
- package/dist/modules/channels/discord-gateway.d.mts +19 -0
- package/dist/modules/channels/discord-gateway.mjs +233 -0
- package/dist/modules/channels/discord-gateway.mjs.map +7 -0
- package/dist/modules/channels/discord.d.mts +9 -0
- package/dist/modules/channels/discord.mjs +904 -0
- package/dist/modules/channels/discord.mjs.map +7 -0
- package/dist/modules/channels/fake-platform.d.mts +2 -7
- package/dist/modules/channels/fake-platform.mjs +2 -38
- package/dist/modules/channels/fake-platform.mjs.map +2 -2
- package/dist/modules/channels/github.d.mts +5 -0
- package/dist/modules/channels/github.mjs +720 -0
- package/dist/modules/channels/github.mjs.map +7 -0
- package/dist/modules/channels/group-messaging.d.mts +2 -2
- package/dist/modules/channels/group-messaging.mjs.map +2 -2
- package/dist/modules/channels/index.d.mts +7 -3
- package/dist/modules/channels/index.mjs +7 -3
- package/dist/modules/channels/index.mjs.map +2 -2
- package/dist/modules/channels/linear.d.mts +4 -0
- package/dist/modules/channels/linear.mjs +621 -0
- package/dist/modules/channels/linear.mjs.map +7 -0
- package/dist/modules/channels/messaging-channel.d.mts +3 -3
- package/dist/modules/channels/messaging-channel.mjs +2 -2
- package/dist/modules/channels/messaging-channel.mjs.map +2 -2
- package/dist/modules/channels/phone.d.mts +8 -0
- package/dist/modules/channels/phone.mjs +382 -0
- package/dist/modules/channels/phone.mjs.map +7 -0
- package/dist/modules/channels/projection.mjs.map +1 -1
- package/dist/modules/channels/reply-receipts.d.mts +31 -0
- package/dist/modules/channels/reply-receipts.mjs +63 -0
- package/dist/modules/channels/reply-receipts.mjs.map +7 -0
- package/dist/modules/channels/routing.d.mts +1 -0
- package/dist/modules/channels/routing.mjs +5 -1
- package/dist/modules/channels/routing.mjs.map +2 -2
- package/dist/modules/channels/slack.d.mts +7 -0
- package/dist/modules/channels/slack.mjs +1134 -0
- package/dist/modules/channels/slack.mjs.map +7 -0
- package/dist/modules/channels/voice-recording.d.mts +7 -0
- package/dist/modules/channels/voice-recording.mjs +49 -0
- package/dist/modules/channels/voice-recording.mjs.map +7 -0
- package/dist/modules/channels/voice.d.mts +33 -0
- package/dist/modules/channels/voice.mjs +851 -0
- package/dist/modules/channels/voice.mjs.map +7 -0
- package/dist/modules/channels/webhook.d.mts +5 -0
- package/dist/modules/channels/webhook.mjs +76 -0
- package/dist/modules/channels/webhook.mjs.map +7 -0
- package/dist/modules/harnesses/acp-describe.mjs +1 -1
- package/dist/modules/harnesses/acp-describe.mjs.map +2 -2
- package/dist/modules/harnesses/fidelity.mjs +17 -17
- package/dist/modules/harnesses/fidelity.mjs.map +2 -2
- package/dist/modules/harnesses/remote-url.mjs.map +1 -1
- package/dist/modules/runtime/remote-host.mjs.map +1 -1
- package/dist/modules/sandboxes/build-scripts.mjs.map +1 -1
- package/dist/modules/sandboxes/bytes.d.mts +1 -0
- package/dist/modules/sandboxes/bytes.mjs +7 -1
- package/dist/modules/sandboxes/bytes.mjs.map +2 -2
- package/dist/modules/sandboxes/computer.mjs +5 -1
- package/dist/modules/sandboxes/computer.mjs.map +2 -2
- package/dist/modules/sandboxes/computers-api-transport.d.mts +1 -1
- package/dist/modules/sandboxes/computers-api-transport.mjs +8 -0
- package/dist/modules/sandboxes/computers-api-transport.mjs.map +2 -2
- package/dist/modules/sandboxes/device-api.mjs +8 -0
- package/dist/modules/sandboxes/device-api.mjs.map +2 -2
- package/dist/modules/sandboxes/device.mjs +6 -0
- package/dist/modules/sandboxes/device.mjs.map +2 -2
- package/dist/modules/sandboxes/paths.mjs.map +1 -1
- package/dist/modules/sandboxes/session.d.mts +1 -0
- package/dist/modules/sandboxes/session.mjs +106 -36
- package/dist/modules/sandboxes/session.mjs.map +3 -3
- package/dist/modules/sandboxes/testing/conformance.mjs +3 -0
- package/dist/modules/sandboxes/testing/conformance.mjs.map +2 -2
- package/dist/modules/sandboxes/testing/fake-machine.mjs +11 -0
- package/dist/modules/sandboxes/testing/fake-machine.mjs.map +2 -2
- package/dist/modules/sandboxes/transport.d.mts +1 -0
- package/dist/modules/sandboxes/transport.mjs.map +2 -2
- package/dist/modules/testing/harness.mjs +2 -2
- package/dist/modules/testing/harness.mjs.map +1 -1
- package/package.json +45 -19
- package/dist/modules/channels/address-context.d.mts +0 -7
- package/dist/modules/channels/address-context.mjs +0 -80
- package/dist/modules/channels/address-context.mjs.map +0 -7
- package/dist/modules/channels/agent-events.d.mts +0 -103
- package/dist/modules/channels/agent-events.mjs +0 -341
- package/dist/modules/channels/agent-events.mjs.map +0 -7
- package/dist/modules/channels/agent-schedules.d.mts +0 -25
- package/dist/modules/channels/agent-schedules.mjs +0 -96
- package/dist/modules/channels/agent-schedules.mjs.map +0 -7
- package/dist/modules/channels/service-input.d.mts +0 -22
- package/dist/modules/channels/service-input.mjs +0 -32
- package/dist/modules/channels/service-input.mjs.map +0 -7
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/channels/linear.ts"],
|
|
4
|
+
"sourcesContent": ["import {\n defineChannel,\n packageRef,\n jsonObjectSchema,\n jsonValueSchema,\n type ChannelEventContextIface,\n type ChannelReceiveContextIface,\n type JsonObject,\n type JsonValue,\n type PlaceRef,\n type Refusal,\n type ChannelReceiveResult,\n} from '@finalai/agent-provider';\nimport * as z from 'zod';\nimport { shareReplyReceipt } from \"./reply-receipts.mjs\";\nimport {\n channelBaseUrl,\n providerPlaceKey,\n confirmOAuth,\n deliveryRetention,\n decisionChoices,\n decisionAnswer,\n decisionSummary,\n externalKey,\n inTenant,\n providerOriginSchema,\n parseJson,\n personFor,\n receivePersonMessage,\n refString,\n responseJson,\n startOAuth,\n stateKey,\n takeOAuth,\n textOf,\n completionText,\n verifyHmac,\n} from \"./channel-utils.mjs\";\nconst configSchema = z.object({ baseUrl: z.string().url().optional() });\ntype Config = z.infer<typeof configSchema>;\nconst pendingDecisionsSchema = z.array(\n z.object({\n decisionId: z.string(),\n user: z.string(),\n choices: z.array(\n z.object({\n label: z.string(),\n outcome: jsonValueSchema,\n answer: z.union([z.literal('approve'), z.literal('deny')]),\n }),\n ),\n }),\n);\n\nconst graphql = async (\n ctx: Pick<ChannelReceiveContextIface<Config>, 'credentials'> &\n Partial<Pick<ChannelEventContextIface<Config>, 'origin'>>,\n query: string,\n variables: JsonObject,\n token?: string,\n): Promise<JsonObject> => {\n const accessToken =\n token ?? (await ctx.credentials.app({ tenant: ctx.origin?.place.tenant })).accessToken;\n const response = z\n .object({\n data: jsonObjectSchema.optional(),\n errors: z.array(z.object({ message: z.string() })).optional(),\n })\n .parse(\n await responseJson(\n await ctx.credentials.fetch('https://api.linear.app/graphql', {\n method: 'POST',\n headers: { authorization: `Bearer ${accessToken}`, 'content-type': 'application/json' },\n body: JSON.stringify({ query, variables }),\n }),\n ),\n );\n if (response.errors != null || response.data == null)\n throw new Error(\n `Linear GraphQL: ${response.errors?.map((error) => error.message).join('; ') ?? 'missing data'}`,\n );\n return response.data;\n};\nconst createActivity = async (\n ctx: Pick<ChannelReceiveContextIface<Config>, 'credentials' | 'store'>,\n sessionId: string,\n content: JsonObject,\n key?: string,\n): Promise<void> => {\n if (\n content.type === 'response' &&\n (typeof content.body !== 'string' || content.body.trim().length === 0)\n )\n return;\n if (key != null && (await ctx.store.get(key)) != null) return;\n const data = await graphql(\n ctx,\n 'mutation ChannelActivity($input: AgentActivityCreateInput!) { agentActivityCreate(input: $input) { success agentActivity { id } } }',\n { input: { agentSessionId: sessionId, content } },\n );\n const result = z\n .object({\n agentActivityCreate: z.object({\n success: z.boolean(),\n agentActivity: z.object({ id: z.string() }).nullish(),\n }),\n })\n .parse(data).agentActivityCreate;\n if (!result.success || result.agentActivity == null) throw new Error('Linear activity rejected');\n if (key != null) await ctx.store.set(key, result.agentActivity.id, deliveryRetention);\n};\nconst activitySession = async (ctx: ChannelEventContextIface<Config>): Promise<string | null> => {\n const savedSession = z\n .string()\n .safeParse(\n await ctx.store.get(stateKey('linear-session-for-place', providerPlaceKey(ctx.origin.place))),\n );\n return (\n refString(ctx.origin.ref, 'sessionId') ??\n (ctx.origin.place.kind === 'agent-session'\n ? ctx.origin.place.id\n : savedSession.success\n ? savedSession.data\n : null)\n );\n};\nconst activity = async (\n ctx: ChannelEventContextIface<Config>,\n content: JsonObject,\n key: string,\n): Promise<void> => {\n const sessionId = await activitySession(ctx);\n if (sessionId != null) await createActivity(ctx, sessionId, content, key);\n};\nconst sessionSchema = z.object({\n type: z.literal('AgentSessionEvent'),\n action: z.string(),\n organizationId: z.string(),\n webhookTimestamp: z.number(),\n agentSession: z.object({\n id: z.string(),\n creatorId: z.string().nullish(),\n creator: z.object({ id: z.string(), name: z.string() }).nullish(),\n issue: z\n .object({ id: z.string(), title: z.string(), description: z.string().nullish() })\n .nullish(),\n comment: z\n .object({ id: z.string(), body: z.string(), userId: z.string().optional() })\n .nullish(),\n }),\n agentActivity: z\n .object({\n id: z.string(),\n userId: z.string(),\n user: z.object({ id: z.string(), name: z.string() }),\n content: jsonObjectSchema,\n signal: z.string().nullish(),\n sourceCommentId: z.string().nullish(),\n })\n .nullish(),\n});\nexport const linearChannel = (options: JsonObject = {}) => {\n const config = configSchema.parse(options);\n return defineChannel<Config, JsonValue, JsonValue>({\n name: 'linear',\n config,\n installed: packageRef('@finalai/final-agent-provider/channels/linear'),\n credentials: { app: 'linear' },\n identity: {\n kind: 'redirect',\n start: async (args, ctx) => {\n const app = await ctx.credentials.appInfo();\n return startOAuth(\n {\n ...args,\n authorizeUrl: 'https://linear.app/oauth/authorize',\n clientId: app.clientId,\n redirectUri: `${channelBaseUrl(config)}/b/${ctx.binding.id}/oauth/callback`,\n scope: 'read',\n extra: { actor: 'user' },\n },\n ctx,\n );\n },\n },\n places: {\n kinds: ['agent-session', 'issue'],\n defaults: (place) =>\n place.kind === 'agent-session' || place.kind === 'issue'\n ? { create: 'group', replies: 'origin', audience: 'members' }\n : null,\n },\n routes: [\n {\n method: 'GET',\n path: '/oauth/callback',\n handler: async (ctx) => {\n const saved = await takeOAuth(ctx.request, ctx);\n if (saved == null)\n return new Response('Invalid or expired link request', { status: 400 });\n const app = await ctx.credentials.appInfo();\n const token = z.object({ access_token: z.string() }).parse(\n await responseJson(\n await ctx.credentials.fetch('https://api.linear.app/oauth/token', {\n method: 'POST',\n body: new URLSearchParams({\n client_id: app.clientId,\n client_secret: app.clientSecret,\n grant_type: 'authorization_code',\n code: saved.code,\n redirect_uri: saved.redirectUri,\n }),\n }),\n ),\n );\n const user = z\n .object({\n viewer: z.object({\n id: z.string(),\n name: z.string(),\n organization: z.object({ id: z.string() }),\n }),\n })\n .parse(\n await graphql(\n ctx,\n 'query ChannelIdentity { viewer { id name organization { id } } }',\n {},\n token.access_token,\n ),\n ).viewer;\n return confirmOAuth(\n {\n ticket: saved.ticket,\n expected: saved.principal,\n actual: { id: user.id, tenant: user.organization.id, displayName: user.name },\n },\n ctx,\n );\n },\n },\n {\n method: 'POST',\n path: '/webhook',\n handler: async (ctx) => {\n const body = await ctx.request.text();\n const app = await ctx.credentials.appInfo();\n const secret = app.signingSecret;\n const signature = ctx.request.headers.get('linear-signature');\n if (\n secret == null ||\n signature == null ||\n !(await verifyHmac({ secret, signature, body }))\n )\n return new Response(null, { status: 401 });\n const raw = jsonValueSchema.parse(parseJson(body));\n const timestamp = z.object({ webhookTimestamp: z.number() }).safeParse(raw);\n if (\n !timestamp.success ||\n Math.abs(Date.now() - timestamp.data.webhookTimestamp) > 300_000\n )\n return new Response(null, { status: 401 });\n await ctx.receive(raw);\n return new Response('ok');\n },\n },\n ],\n receive: async (input, baseContext) => {\n const tenant = z.object({ organizationId: z.string() }).safeParse(input);\n const ctx = tenant.success ? inTenant(baseContext, tenant.data.organizationId) : baseContext;\n const lifecycle = z.object({ type: z.string(), action: z.string() }).safeParse(input);\n if (\n lifecycle.success &&\n lifecycle.data.type === 'OAuthApp' &&\n lifecycle.data.action === 'revoked'\n ) {\n if (!tenant.success) return { ignored: true, reason: 'missing-tenant' };\n await ctx.people.revoke({ tenant: tenant.data.organizationId, reason: 'uninstalled' });\n return { accepted: true };\n }\n const mutation = z\n .object({\n type: z.string(),\n action: z.string(),\n actor: z.object({ id: z.string() }).optional(),\n data: z.object({\n id: z.string(),\n body: z.string().optional(),\n userId: z.string().optional(),\n commentId: z.string().optional(),\n emoji: z.string().optional(),\n }),\n })\n .safeParse(input);\n if (\n mutation.success &&\n (mutation.data.type === 'Comment' || mutation.data.type === 'Reaction')\n ) {\n const event = mutation.data;\n const saved = providerOriginSchema.safeParse(\n await ctx.store.get(stateKey('linear-origin', event.data.commentId ?? event.data.id)),\n );\n if (!saved.success) return { ignored: true, reason: 'unknown-origin' };\n const userId = event.actor?.id ?? event.data.userId;\n if (userId == null || !tenant.success) return { ignored: true, reason: 'missing-actor' };\n const by = await personFor(ctx, { id: userId, tenant: tenant.data.organizationId });\n if (typeof by !== 'string') return { accepted: false, refusal: by };\n const route = await ctx.places.resolve(saved.data.place);\n if (route.kind !== 'connected') return { ignored: true, reason: 'unconnected' };\n const conversation = ctx.conversation(route.conversation);\n if (event.type === 'Comment' && event.action === 'update' && event.data.body != null) {\n const result = await conversation.edit({\n externalKey: saved.data.externalKey,\n by,\n content: [{ type: 'text', text: event.data.body }],\n });\n return 'refused' in result ? { accepted: false, refusal: result } : { accepted: true };\n }\n if (event.type === 'Comment' && event.action === 'remove') {\n const result = await conversation.remove({ externalKey: saved.data.externalKey, by });\n return 'refused' in result ? { accepted: false, refusal: result } : { accepted: true };\n }\n if (event.type === 'Reaction' && event.data.emoji != null) {\n const result = await conversation.react({\n target: { externalKey: saved.data.externalKey },\n by,\n emoji: event.data.emoji,\n op: event.action === 'remove' ? 'remove' : 'add',\n });\n return 'refused' in result ? { accepted: false, refusal: result } : { accepted: true };\n }\n return { ignored: true, reason: 'unsupported-mutation' };\n }\n const parsed = sessionSchema.safeParse(input);\n if (!parsed.success) return { ignored: true, reason: 'not-agent-session' };\n const event = parsed.data;\n if (event.action !== 'created' && event.action !== 'prompted')\n return { ignored: true, reason: 'unsupported-action' };\n const session = event.agentSession;\n const user = event.agentActivity?.userId ?? session.creatorId;\n if (user == null) return { ignored: true, reason: 'no-human-requester' };\n const principal = {\n id: user,\n tenant: event.organizationId,\n displayName: event.agentActivity?.user.name ?? session.creator?.name ?? user,\n };\n const content = event.agentActivity?.content.body;\n const text =\n typeof content === 'string'\n ? content\n : (session.comment?.body ?? session.issue?.title ?? 'Please help with this issue.');\n const by = await personFor(ctx, principal);\n const routePlace: PlaceRef =\n session.issue == null\n ? { kind: 'agent-session', id: session.id, tenant: event.organizationId }\n : {\n kind: 'issue',\n id: session.issue.id,\n tenant: event.organizationId,\n title: session.issue.title,\n };\n const place: PlaceRef =\n session.issue == null\n ? routePlace\n : {\n kind: 'agent-session',\n id: session.id,\n tenant: event.organizationId,\n title: session.issue.title,\n parent: routePlace,\n };\n if (event.agentActivity?.signal === 'stop') {\n const activityId = event.agentActivity.id;\n const refuseStop = async (reason: Refusal): Promise<ChannelReceiveResult> => {\n await createActivity(\n ctx,\n session.id,\n {\n type: 'error',\n body: 'Could not stop this run from this account. Open Final to manage it.',\n },\n stateKey('linear-stop-refused', session.id, activityId),\n );\n return { accepted: false, refusal: reason };\n };\n const route = await ctx.places.resolve(routePlace);\n if (route.kind !== 'connected')\n return refuseStop({\n refused: 'unconnected',\n message: 'This place is not connected to Final.',\n connectUrl: route.connectUrl,\n });\n if (\n typeof by !== 'string' &&\n !(\n by.refused === 'unlinked' &&\n route.policy.audience === 'anyone' &&\n session.creatorId === user\n )\n )\n return refuseStop(by);\n const key = stateKey(\n 'linear-stop',\n providerPlaceKey(place),\n session.id,\n event.agentActivity.id,\n );\n const saved = z.string().safeParse(await ctx.store.get(key));\n let message: string;\n if (saved.success) message = saved.data;\n else {\n const target = z\n .object({ turnId: z.string() })\n .safeParse(await ctx.store.get(stateKey('linear-session-turn', session.id)));\n if (!target.success)\n message =\n 'There is no accepted turn in this session to stop. Open Final to manage pending work.';\n else {\n const cancelled = await ctx.turns.cancel({\n conversation: route.conversation,\n by: typeof by === 'string' ? by : undefined,\n turnId: target.data.turnId,\n });\n if ('refused' in cancelled) return refuseStop(cancelled);\n message = cancelled.accepted\n ? 'The agent has stopped.'\n : 'There is no active turn this account may stop. Continue in Final.';\n }\n await ctx.store.set(key, message, deliveryRetention);\n }\n await createActivity(\n ctx,\n session.id,\n { type: 'response', body: message },\n stateKey('linear-stop-confirmed', session.id, event.agentActivity.id),\n );\n return { accepted: true };\n }\n const answerKey =\n event.action === 'prompted' && event.agentActivity != null\n ? stateKey('linear-answer-activity', session.id, event.agentActivity.id)\n : null;\n const replay = z\n .object({ decisionId: z.string(), outcome: jsonValueSchema })\n .safeParse(answerKey == null ? null : await ctx.store.get(answerKey));\n if (replay.success) {\n if (typeof by !== 'string') return { accepted: false, refusal: by };\n const result = await ctx.decisions.resolve({ ...replay.data, by });\n return 'refused' in result ? { accepted: false, refusal: result } : { accepted: true };\n }\n const pending = pendingDecisionsSchema.safeParse(\n (await ctx.store.get(stateKey('linear-decision', session.id))) ?? [],\n );\n const owned = pending.success ? pending.data.filter((entry) => entry.user === user) : [];\n if (event.action === 'prompted' && owned.length > 0 && typeof by === 'string') {\n const normalized = text\n .trim()\n .toLowerCase()\n .replace(/[.!?]$/g, '');\n const answer = ['yes', 'no', 'approve', 'deny'].includes(normalized);\n if (pending.success && pending.data.length > 1) {\n if (answer || owned.some((entry) => decisionAnswer(entry.choices, text) != null)) {\n await createActivity(ctx, session.id, {\n type: 'response',\n body: 'Multiple actions need approval. Answer them in Final.',\n });\n return { accepted: true };\n }\n } else {\n const current = owned[0];\n const choice = current == null ? null : decisionAnswer(current.choices, text);\n if (current != null && choice != null) {\n if (answerKey != null)\n await ctx.store.set(\n answerKey,\n { decisionId: current.decisionId, outcome: choice.outcome },\n deliveryRetention,\n );\n const result = await ctx.decisions.resolve({\n decisionId: current.decisionId,\n outcome: choice.outcome,\n by,\n });\n if ('refused' in result) {\n await createActivity(ctx, session.id, { type: 'error', body: result.message });\n return { accepted: false, refusal: result };\n }\n const latest = pendingDecisionsSchema.safeParse(\n (await ctx.store.get(stateKey('linear-decision', session.id))) ?? [],\n );\n if (latest.success) {\n const remaining = latest.data.filter(\n (entry) => entry.decisionId !== current.decisionId,\n );\n await ctx.store.set(\n stateKey('linear-decision', session.id),\n remaining.length === 0 ? null : jsonValueSchema.parse(remaining),\n { ttlSeconds: 3600 },\n );\n }\n if (!result.resolved) {\n const message = 'This decision is no longer waiting in Final.';\n await createActivity(ctx, session.id, { type: 'error', body: message });\n return { accepted: false, refusal: { refused: 'no-access', message } };\n }\n return { accepted: true };\n }\n if (answer) {\n await createActivity(ctx, session.id, {\n type: 'response',\n body: 'Choose the exact approval option in Final.',\n });\n return { accepted: true };\n }\n }\n }\n await createActivity(\n ctx,\n session.id,\n { type: 'thought', body: 'I\u2019m looking into this.' },\n stateKey('linear-ack', session.id, event.agentActivity?.id ?? 'created'),\n );\n await ctx.store.set(\n stateKey('linear-session-for-place', providerPlaceKey(routePlace)),\n session.id,\n deliveryRetention,\n );\n await ctx.store.set(\n stateKey('linear-session-for-place', providerPlaceKey(place)),\n session.id,\n deliveryRetention,\n );\n const key = externalKey(place, event.agentActivity?.id ?? session.comment?.id ?? session.id);\n const sourceComment =\n event.agentActivity?.sourceCommentId ??\n (event.agentActivity == null ? session.comment?.id : undefined);\n if (sourceComment != null)\n await ctx.store.set(\n stateKey('linear-origin', sourceComment),\n jsonValueSchema.parse({ place, externalKey: key }),\n deliveryRetention,\n );\n const result = await receivePersonMessage({\n ctx,\n principal,\n place,\n routePlace,\n onAccepted: async ({ turnId }) => {\n if (turnId != null)\n await ctx.store.set(\n stateKey('linear-session-turn', session.id),\n { turnId },\n deliveryRetention,\n );\n },\n text,\n externalKey: key,\n context:\n session.issue == null\n ? []\n : [\n `Issue: ${session.issue.title}`,\n ...(session.issue.description == null ? [] : [session.issue.description]),\n ],\n ref: {\n externalKey: key,\n sessionId: session.id,\n requester: user,\n tenant: event.organizationId,\n },\n });\n if ('accepted' in result && !result.accepted) {\n await createActivity(\n ctx,\n session.id,\n {\n type: 'response',\n body: `${result.refusal.message} ${result.refusal.linkUrl ?? result.refusal.connectUrl ?? ''}`,\n },\n stateKey('linear-refusal', key),\n );\n await createActivity(\n ctx,\n session.id,\n {\n type: 'error',\n body: 'Open Final to resolve this request, then mention the agent again.',\n },\n stateKey('linear-refusal-error', key),\n );\n }\n return result;\n },\n events: {\n 'turn.started': async (data, ctx) => {\n const sessionId =\n refString(ctx.origin.ref, 'sessionId') ??\n (ctx.origin.place.kind === 'agent-session' ? ctx.origin.place.id : null);\n if (sessionId != null)\n await ctx.store.set(\n stateKey('linear-session-turn', sessionId),\n { turnId: data.turnId },\n deliveryRetention,\n );\n },\n 'message.posted': async (data, ctx) =>\n activity(\n ctx,\n {\n type: 'response',\n body: `From ${data.author.displayName} in Final\\n\\n${textOf(data.message.content)}`,\n },\n stateKey('linear-mirror', providerPlaceKey(ctx.origin.place), data.message.id),\n ),\n 'turn.completed': async (data, ctx) => {\n const sessionId = await activitySession(ctx);\n if (sessionId == null) return;\n await shareReplyReceipt({\n bindingId: ctx.binding.id,\n store: ctx.store,\n destination: stateKey('linear', ctx.origin.place.tenant ?? null, sessionId),\n messageIds: data.messages?.map((message) => message.id) ?? [],\n originKey: stateKey('linear-response', providerPlaceKey(ctx.origin.place), ctx.turn.id),\n send: (key) =>\n createActivity(\n ctx,\n sessionId,\n {\n type:\n data.stopReason === 'error' || data.stopReason === 'timed_out'\n ? 'error'\n : 'response',\n body: completionText(data),\n },\n key,\n ),\n });\n },\n 'tool.started': async (data, ctx) =>\n activity(\n ctx,\n { type: 'action', action: data.title, parameter: 'In progress' },\n stateKey(\n 'linear-tool',\n providerPlaceKey(ctx.origin.place),\n ctx.turn.id,\n data.toolCallId,\n 'started',\n ),\n ),\n 'tool.completed': async (data, ctx) =>\n activity(\n ctx,\n {\n type: 'action',\n action: data.title,\n parameter: data.status,\n result: data.content == null ? data.status : textOf(data.content),\n },\n stateKey(\n 'linear-tool',\n providerPlaceKey(ctx.origin.place),\n ctx.turn.id,\n data.toolCallId,\n 'completed',\n ),\n ),\n 'plan.updated': async (data, ctx) => {\n const sessionId =\n refString(ctx.origin.ref, 'sessionId') ??\n (ctx.origin.place.kind === 'agent-session' ? ctx.origin.place.id : null);\n if (sessionId == null) return;\n const plan = data.entries.flatMap((entry) =>\n typeof entry.content === 'string'\n ? [\n {\n content: entry.content,\n status:\n entry.status === 'completed'\n ? 'completed'\n : entry.status === 'in_progress' || entry.status === 'inProgress'\n ? 'inProgress'\n : entry.status === 'canceled'\n ? 'canceled'\n : 'pending',\n },\n ]\n : [],\n );\n await graphql(\n ctx,\n 'mutation ChannelPlan($id: String!, $input: AgentSessionUpdateInput!) { agentSessionUpdate(id: $id, input: $input) { success } }',\n { id: sessionId, input: { plan } },\n );\n },\n 'decision.resolved': async (data, ctx) => {\n const sessionId =\n refString(ctx.origin.ref, 'sessionId') ??\n (ctx.origin.place.kind === 'agent-session' ? ctx.origin.place.id : null);\n if (sessionId == null) return;\n const pending = pendingDecisionsSchema.safeParse(\n (await ctx.store.get(stateKey('linear-decision', sessionId))) ?? [],\n );\n if (pending.success) {\n const remaining = pending.data.filter((entry) => entry.decisionId !== data.decisionId);\n await ctx.store.set(\n stateKey('linear-decision', sessionId),\n remaining.length === 0 ? null : jsonValueSchema.parse(remaining),\n { ttlSeconds: 3600 },\n );\n }\n },\n 'decision.created': async (data, ctx) => {\n const sessionId =\n refString(ctx.origin.ref, 'sessionId') ??\n (ctx.origin.place.kind === 'agent-session' ? ctx.origin.place.id : null);\n const user = refString(ctx.origin.ref, 'requester');\n if (sessionId == null || user == null) return;\n const choices = decisionChoices(data.request);\n const pending = pendingDecisionsSchema.safeParse(\n (await ctx.store.get(stateKey('linear-decision', sessionId))) ?? [],\n );\n const existing = pending.success\n ? pending.data.filter((entry) => entry.decisionId !== data.decisionId)\n : [];\n await ctx.store.set(\n stateKey('linear-decision', sessionId),\n jsonValueSchema.parse([...existing, { decisionId: data.decisionId, user, choices }]),\n { ttlSeconds: 3600 },\n );\n await activity(\n ctx,\n {\n type: 'elicitation',\n body: `${decisionSummary(data.request)}\\n${choices.length === 0 ? 'Answer in Final \u2192' : choices.map((choice) => choice.label).join(' / ')}`,\n },\n stateKey('linear-elicitation', providerPlaceKey(ctx.origin.place), data.decisionId),\n );\n },\n },\n });\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["event", "by", "result", "key"]
|
|
7
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { TurnPolicy, type
|
|
1
|
+
import { TurnPolicy, type RoomSurfaceDefinitionIface, type RoomReceiveContextIface, type ConversationRefIface, type JsonValue, type RouteArgsIface, type RouteDecisionIface } from '@finalai/agent-provider';
|
|
2
2
|
import { type ProjectorConfigIface } from "./projection.mjs";
|
|
3
3
|
export interface MessagingChannelConfigIface extends ProjectorConfigIface {
|
|
4
4
|
route?: (args: RouteArgsIface) => RouteDecisionIface[] | Promise<RouteDecisionIface[]>;
|
|
5
5
|
}
|
|
6
|
-
export declare const createConversationReceive: (turnPolicy: TurnPolicy, route?: NonNullable<MessagingChannelConfigIface["route"]>) => (input: JsonValue, ctx:
|
|
7
|
-
export declare const createMessagingChannel: (config?: MessagingChannelConfigIface) =>
|
|
6
|
+
export declare const createConversationReceive: (turnPolicy: TurnPolicy, route?: NonNullable<MessagingChannelConfigIface["route"]>) => (input: JsonValue, ctx: RoomReceiveContextIface) => Promise<ConversationRefIface>;
|
|
7
|
+
export declare const createMessagingChannel: (config?: MessagingChannelConfigIface) => RoomSurfaceDefinitionIface<JsonValue, JsonValue>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
defineRoomSurface,
|
|
3
3
|
TurnPolicy
|
|
4
4
|
} from "@finalai/agent-provider";
|
|
5
5
|
import { contentBlocksOf } from "./content.mjs";
|
|
@@ -85,7 +85,7 @@ const createConversationReceive = (turnPolicy, route = routeMessage) => async (i
|
|
|
85
85
|
const createMessagingChannel = (config = {}) => {
|
|
86
86
|
const turnPolicy = config.turnPolicy ?? TurnPolicy.Steer;
|
|
87
87
|
const projector = createProjector({ ...config, turnPolicy });
|
|
88
|
-
return
|
|
88
|
+
return defineRoomSurface({
|
|
89
89
|
receive: createConversationReceive(turnPolicy, config.route),
|
|
90
90
|
route: config.route ?? routeMessage,
|
|
91
91
|
turnPolicy,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/channels/messaging-channel.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n
|
|
5
|
-
"mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,OAOK;AACP,SAAS,uBAAuB;AAChC,SAAS,gBAAgB,kBAAkB,sBAAsB;AACjE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,uBAAkD;AAC3D,SAAS,oBAAoB;AAQ7B,MAAM,2BAA2B,OAC/B,OACA,QACkC;AAClC,QAAM,eAAe,MAAM,IAAI,SAAS,cAAc,aAAa,MAAM,cAAc;AACvF,MAAI,MAAM,UAAU,sBAAsB,YAAY;AACpD,UAAM,UAAU,aAAa,KAAK,CAAC,gBAAgB,YAAY,OAAO,MAAM,aAAa;AACzF,QAAI,WAAW,MAAM;AACnB,YAAM,iBAAiB;AAAA,QACrB,UAAU,IAAI;AAAA,QACd,cAAc,aAAa;AAAA,UACzB,CAAC,gBACC,CAAC,YAAY,SACZ,YAAY,SAAS,WACpB,IAAI,sBAAsB,QAC1B,YAAY,OAAO,IAAI;AAAA,QAC7B;AAAA,QACA;AAAA,QACA,QAAQ,MAAM;AAAA,QACd,gBAAgB,cAAc,MAAM,aAAa,IAAI,MAAM,OAAO;AAAA,MACpE,CAAC;AAAA,IACH;AACA,WAAO,EAAE,gBAAgB,MAAM,eAAe;AAAA,EAChD;AACA,QAAM,CAAC,OAAO,KACZ,MAAM,IAAI,SAAS,cAAc,eAAe;AAAA,IAC9C,gBAAgB,MAAM;AAAA,IACtB,WAAW,MAAM,aAAa;AAAA,IAC9B,OAAO;AAAA,EACT,CAAC,GACD,OAAO,CAAC,cAAc,UAAU,cAAc,MAAM,SAAS;AAC/D,MACE,WAAW,SACV,IAAI,sBAAsB,QAAQ,QAAQ,wBAAwB,IAAI,qBACvE;AACA,UAAM,eAAe;AAAA,MACnB,UAAU,IAAI;AAAA,MACd;AAAA,MACA;AAAA,MACA,kBAAkB,MAAM;AAAA,MACxB,sBAAsB,MAAM;AAAA,MAC5B,OAAO,MAAM;AAAA,MACb,SAAS,MAAM;AAAA,IACjB,CAAC;AAAA,EACH;AACA,SAAO,EAAE,gBAAgB,MAAM,eAAe;AAChD;AAIO,MAAM,4BACX,CACE,YACA,QAA2D,iBAE7D,OAAO,OAAkB,
|
|
4
|
+
"sourcesContent": ["import {\n defineRoomSurface,\n TurnPolicy,\n type RoomSurfaceDefinitionIface,\n type RoomReceiveContextIface,\n type ConversationRefIface,\n type JsonValue,\n type RouteArgsIface,\n type RouteDecisionIface,\n} from '@finalai/agent-provider';\nimport { contentBlocksOf } from \"./content.mjs\";\nimport { deliverMessage, membershipNotice, reactionNotice } from \"./delivery.mjs\";\nimport {\n conversationEventInboundSchema,\n ConversationEventKind,\n messageInboundSchema,\n type ConversationEventInboundIface,\n} from \"./inbound.mjs\";\nimport { createProjector, type ProjectorConfigIface } from \"./projection.mjs\";\nimport { routeMessage } from \"./routing.mjs\";\n\n// `turnPolicy` (ProjectorConfigIface): steer unless configured; a conversation\n// set to queue overrides it.\nexport interface MessagingChannelConfigIface extends ProjectorConfigIface {\n route?: (args: RouteArgsIface) => RouteDecisionIface[] | Promise<RouteDecisionIface[]>;\n}\n\nconst receiveConversationEvent = async (\n event: ConversationEventInboundIface,\n ctx: RoomReceiveContextIface,\n): Promise<ConversationRefIface> => {\n const participants = await ctx.platform.conversations.participants(event.conversationId);\n if (event.event === ConversationEventKind.Membership) {\n const subject = participants.find((participant) => participant.id === event.participantId);\n if (subject != null) {\n await membershipNotice({\n platform: ctx.platform,\n participants: participants.filter(\n (participant) =>\n !participant.left &&\n (participant.kind !== 'agent' ||\n ctx.agentParticipantId == null ||\n participant.id === ctx.agentParticipantId),\n ),\n subject,\n change: event.change,\n idempotencyKey: `membership:${event.participantId}:${event.eventId}`,\n });\n }\n return { conversationId: event.conversationId };\n }\n const [message] = (\n await ctx.platform.conversations.recentMessages({\n conversationId: event.conversationId,\n beforeSeq: event.messageSeq + 1,\n limit: 1,\n })\n ).filter((candidate) => candidate.messageId === event.messageId);\n if (\n message != null &&\n (ctx.agentParticipantId == null || message.authorParticipantId === ctx.agentParticipantId)\n ) {\n await reactionNotice({\n platform: ctx.platform,\n participants,\n message,\n isLatestOfAuthor: event.latestOfAuthor,\n reactorParticipantId: event.reactorParticipantId,\n emoji: event.emoji,\n eventId: event.eventId,\n });\n }\n return { conversationId: event.conversationId };\n};\n\n// Persists the message before routing and delivering it to sessions,\n// using the channel's turnPolicy.\nexport const createConversationReceive =\n (\n turnPolicy: TurnPolicy,\n route: NonNullable<MessagingChannelConfigIface['route']> = routeMessage,\n ) =>\n async (input: JsonValue, ctx: RoomReceiveContextIface): Promise<ConversationRefIface> => {\n const event = conversationEventInboundSchema.safeParse(input);\n if (event.success) {\n return receiveConversationEvent(event.data, ctx);\n }\n const inbound = messageInboundSchema.parse(input);\n const conversation =\n ctx.conversation ?? (await ctx.platform.conversations.get(inbound.conversationId));\n if (conversation == null) {\n throw new Error(`conversation ${inbound.conversationId} not found`);\n }\n const message =\n ctx.message ??\n (await ctx.platform.conversations.postMessage({\n conversationId: inbound.conversationId,\n authorParticipantId: inbound.authorParticipantId,\n kind: inbound.kind,\n content: contentBlocksOf(inbound.content),\n clientMessageId: inbound.clientMessageId ?? null,\n replyToMessageId: inbound.replyToMessageId ?? null,\n threadRootId: inbound.threadRootId ?? null,\n quotedMessageIds: inbound.quotedMessageIds,\n mentionedParticipantIds: inbound.mentionedParticipantIds,\n }));\n await deliverMessage({\n platform: ctx.platform,\n route: async (args) => {\n const requested =\n ctx.requestedDelivery != null && ctx.agentParticipantId != null && route === routeMessage\n ? [{ participantId: ctx.agentParticipantId, delivery: ctx.requestedDelivery }]\n : await route({ ...args, agentParticipantId: ctx.agentParticipantId });\n return requested.filter(\n (decision) =>\n ctx.agentParticipantId == null || decision.participantId === ctx.agentParticipantId,\n );\n },\n conversation,\n message,\n participants: ctx.participants == null ? undefined : [...ctx.participants],\n turnPolicy,\n });\n return { conversationId: conversation.conversationId };\n };\n\n// The shared body of direct-messaging and group-messaging: one routing table\n// and one projection; the conversation kind selects the rules.\nexport const createMessagingChannel = (\n config: MessagingChannelConfigIface = {},\n): RoomSurfaceDefinitionIface<JsonValue, JsonValue> => {\n const turnPolicy = config.turnPolicy ?? TurnPolicy.Steer;\n const projector = createProjector({ ...config, turnPolicy });\n return defineRoomSurface<JsonValue, JsonValue>({\n receive: createConversationReceive(turnPolicy, config.route),\n route: config.route ?? routeMessage,\n turnPolicy,\n project: projector.project,\n });\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type JsonObject, type JsonValue } from '@finalai/agent-provider';
|
|
2
|
+
export declare const phoneChannel: (options: JsonObject) => import("@finalai/agent-provider").ChannelDefinitionIface<{
|
|
3
|
+
accountSid: string;
|
|
4
|
+
fromNumber: string;
|
|
5
|
+
audience: "members" | "anyone";
|
|
6
|
+
baseUrl?: string | undefined;
|
|
7
|
+
voice?: string | undefined;
|
|
8
|
+
}, JsonValue, JsonValue>;
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineChannel,
|
|
3
|
+
packageRef
|
|
4
|
+
} from "@finalai/agent-provider";
|
|
5
|
+
import * as z from "zod";
|
|
6
|
+
import {
|
|
7
|
+
channelBaseUrl,
|
|
8
|
+
abortable,
|
|
9
|
+
createQueue,
|
|
10
|
+
parseJson,
|
|
11
|
+
responseJson,
|
|
12
|
+
stateKey,
|
|
13
|
+
verifyHmac
|
|
14
|
+
} from "./channel-utils.mjs";
|
|
15
|
+
import { runVoiceConnection } from "./voice.mjs";
|
|
16
|
+
const configSchema = z.object({
|
|
17
|
+
baseUrl: z.string().url().optional(),
|
|
18
|
+
accountSid: z.string(),
|
|
19
|
+
fromNumber: z.string(),
|
|
20
|
+
voice: z.string().optional(),
|
|
21
|
+
audience: z.union([z.literal("members"), z.literal("anyone")]).default("members")
|
|
22
|
+
});
|
|
23
|
+
const activeLinkDeliveries = /* @__PURE__ */ new Set();
|
|
24
|
+
const xml = (value) => value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
25
|
+
const twiml = (body) => new Response(`<Response>${body}</Response>`, { headers: { "content-type": "application/xml" } });
|
|
26
|
+
const callSchema = z.object({ CallSid: z.string(), From: z.string(), To: z.string() });
|
|
27
|
+
const signedRequest = async (request, token, params) => {
|
|
28
|
+
const signature = request.headers.get("x-twilio-signature");
|
|
29
|
+
if (signature == null) return false;
|
|
30
|
+
const body = request.url + [...new Set(params.keys())].sort().flatMap((key) => [...new Set(params.getAll(key))].sort().map((value) => key + value)).join("");
|
|
31
|
+
return verifyHmac({ secret: token, body, signature, algorithm: "SHA-1", encoding: "base64" });
|
|
32
|
+
};
|
|
33
|
+
const hash = async (text) => [...new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text)))].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
34
|
+
const toBase64 = (bytes) => {
|
|
35
|
+
let binary = "";
|
|
36
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
37
|
+
return btoa(binary);
|
|
38
|
+
};
|
|
39
|
+
const sendSms = async (ctx, config, to, body) => {
|
|
40
|
+
const token = await ctx.credentials.secret("twilio-auth-token");
|
|
41
|
+
if (token == null) throw new Error("Twilio credential is unavailable");
|
|
42
|
+
await responseJson(
|
|
43
|
+
await ctx.credentials.fetch(
|
|
44
|
+
`https://api.twilio.com/2010-04-01/Accounts/${config.accountSid}/Messages.json`,
|
|
45
|
+
{
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: { authorization: `Basic ${btoa(`${config.accountSid}:${token}`)}` },
|
|
48
|
+
body: new URLSearchParams({ To: to, From: config.fromNumber, Body: body })
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
const streamResponse = async (ctx, config, call, announced) => {
|
|
54
|
+
const nonce = crypto.randomUUID();
|
|
55
|
+
await ctx.store.set(
|
|
56
|
+
stateKey("twilio-call", nonce),
|
|
57
|
+
{ id: call.CallSid, from: call.From, to: call.To },
|
|
58
|
+
{ ttlSeconds: 600 }
|
|
59
|
+
);
|
|
60
|
+
const url = `${channelBaseUrl(config).replace(/^http/, "ws")}/b/${ctx.binding.id}/stream`;
|
|
61
|
+
return twiml(
|
|
62
|
+
`${announced ? "" : "<Say>This call is recorded and saved in Final. You can hang up to end the recording.</Say>"}<Connect><Stream url="${xml(url)}"><Parameter name="ticket" value="${nonce}" /></Stream></Connect>`
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
const phoneChannel = (options) => {
|
|
66
|
+
const config = configSchema.parse(options);
|
|
67
|
+
return defineChannel({
|
|
68
|
+
name: "phone",
|
|
69
|
+
config,
|
|
70
|
+
installed: packageRef("@finalai/final-agent-provider/channels/phone"),
|
|
71
|
+
credentials: { app: null, secrets: ["twilio-auth-token"] },
|
|
72
|
+
identity: {
|
|
73
|
+
kind: "challenge",
|
|
74
|
+
deliver: async (args, ctx) => sendSms(
|
|
75
|
+
ctx,
|
|
76
|
+
config,
|
|
77
|
+
args.principal.id,
|
|
78
|
+
`Your Final verification code is ${args.code}. It expires in 10 minutes.`
|
|
79
|
+
)
|
|
80
|
+
},
|
|
81
|
+
places: {
|
|
82
|
+
kinds: ["call", "number"],
|
|
83
|
+
defaults: (place) => place.kind === "call" ? {
|
|
84
|
+
create: config.audience === "anyone" ? "group" : "direct-with-agent",
|
|
85
|
+
replies: "origin",
|
|
86
|
+
audience: config.audience
|
|
87
|
+
} : null
|
|
88
|
+
},
|
|
89
|
+
receive: async () => ({ ignored: true, reason: "phone-uses-stream" }),
|
|
90
|
+
routes: [
|
|
91
|
+
{
|
|
92
|
+
method: "POST",
|
|
93
|
+
path: "/webhook",
|
|
94
|
+
handler: async (ctx) => {
|
|
95
|
+
const params = new URLSearchParams(await ctx.request.text());
|
|
96
|
+
const token = await ctx.credentials.secret("twilio-auth-token");
|
|
97
|
+
if (token == null || !await signedRequest(ctx.request, token, params))
|
|
98
|
+
return new Response(null, { status: 401 });
|
|
99
|
+
const call = callSchema.safeParse(Object.fromEntries(params));
|
|
100
|
+
if (!call.success) return new Response("Invalid call", { status: 400 });
|
|
101
|
+
const person = await ctx.people.resolve({ id: call.data.From });
|
|
102
|
+
if (person.kind === "blocked")
|
|
103
|
+
return twiml("<Say>This account cannot use this line.</Say><Hangup/>");
|
|
104
|
+
if (person.kind === "unlinked") {
|
|
105
|
+
if (config.audience === "anyone") return streamResponse(ctx, config, call.data, false);
|
|
106
|
+
if (!/^\+[1-9]\d{1,14}$/.test(call.data.From))
|
|
107
|
+
return twiml(
|
|
108
|
+
"<Say>Please call with caller ID enabled from a number that can receive SMS.</Say><Hangup/>"
|
|
109
|
+
);
|
|
110
|
+
if (activeLinkDeliveries.has(ctx.binding.id))
|
|
111
|
+
return twiml(
|
|
112
|
+
"<Say>Please wait a moment before requesting another link.</Say><Hangup/>"
|
|
113
|
+
);
|
|
114
|
+
activeLinkDeliveries.add(ctx.binding.id);
|
|
115
|
+
try {
|
|
116
|
+
const receipt = stateKey("twilio-link-call", call.data.CallSid);
|
|
117
|
+
if (await ctx.store.get(receipt) == null) {
|
|
118
|
+
const cooldown = stateKey("twilio-link-cooldown", call.data.From);
|
|
119
|
+
if (await ctx.store.get(cooldown) != null)
|
|
120
|
+
return twiml(
|
|
121
|
+
"<Say>Please wait a minute before requesting another link.</Say><Hangup/>"
|
|
122
|
+
);
|
|
123
|
+
const window = Math.floor(Date.now() / 6e4);
|
|
124
|
+
const budgetKey = stateKey("twilio-link-budget", ctx.binding.id);
|
|
125
|
+
const budget = z.object({ window: z.number(), count: z.number() }).safeParse(await ctx.store.get(budgetKey));
|
|
126
|
+
const count = budget.success && budget.data.window === window ? budget.data.count : 0;
|
|
127
|
+
if (count >= 5)
|
|
128
|
+
return twiml(
|
|
129
|
+
"<Say>This line has reached its SMS limit. Please wait a minute and call again.</Say><Hangup/>"
|
|
130
|
+
);
|
|
131
|
+
await ctx.store.set(budgetKey, { window, count: count + 1 }, { ttlSeconds: 120 });
|
|
132
|
+
await ctx.store.set(cooldown, true, { ttlSeconds: 60 });
|
|
133
|
+
try {
|
|
134
|
+
await sendSms(
|
|
135
|
+
ctx,
|
|
136
|
+
config,
|
|
137
|
+
call.data.From,
|
|
138
|
+
`Link this phone number to Final: ${person.linkUrl} Sign in, verify the SMS code, then call again.`
|
|
139
|
+
);
|
|
140
|
+
} catch {
|
|
141
|
+
return twiml(
|
|
142
|
+
"<Say>The account link could not be sent. Please wait a minute and call again.</Say><Hangup/>"
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
await ctx.store.set(receipt, true, { ttlSeconds: 600 });
|
|
146
|
+
}
|
|
147
|
+
return twiml(
|
|
148
|
+
"<Say>A link was sent to your phone. Open it, sign in to Final and verify this number, then call again.</Say><Hangup/>"
|
|
149
|
+
);
|
|
150
|
+
} finally {
|
|
151
|
+
activeLinkDeliveries.delete(ctx.binding.id);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const key = stateKey("twilio-proof", call.data.CallSid);
|
|
155
|
+
const saved = await ctx.store.get(key);
|
|
156
|
+
if (saved == null) {
|
|
157
|
+
const cooldown = stateKey("twilio-sms-cooldown", call.data.From);
|
|
158
|
+
if (await ctx.store.get(cooldown) != null)
|
|
159
|
+
return twiml(
|
|
160
|
+
"<Say>Please wait a minute before requesting another verification code.</Say><Hangup/>"
|
|
161
|
+
);
|
|
162
|
+
await ctx.store.set(cooldown, true, { ttlSeconds: 60 });
|
|
163
|
+
const code = String(crypto.getRandomValues(new Uint32Array(1))[0] % 1e6).padStart(
|
|
164
|
+
6,
|
|
165
|
+
"0"
|
|
166
|
+
);
|
|
167
|
+
await ctx.store.set(
|
|
168
|
+
key,
|
|
169
|
+
{ from: call.data.From, to: call.data.To, hash: await hash(code), attempts: 0 },
|
|
170
|
+
{ ttlSeconds: 300 }
|
|
171
|
+
);
|
|
172
|
+
try {
|
|
173
|
+
await sendSms(
|
|
174
|
+
ctx,
|
|
175
|
+
config,
|
|
176
|
+
call.data.From,
|
|
177
|
+
`Enter ${code} on your Final call to verify it. This code is for this call only.`
|
|
178
|
+
);
|
|
179
|
+
} catch {
|
|
180
|
+
await ctx.store.set(key, null);
|
|
181
|
+
return twiml(
|
|
182
|
+
"<Say>The verification code could not be sent. Please wait a minute and call again.</Say><Hangup/>"
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const action = `${channelBaseUrl(config)}/b/${ctx.binding.id}/verify`;
|
|
187
|
+
return twiml(
|
|
188
|
+
`<Say>This call will be recorded and saved to your Final chat after verification.</Say><Gather numDigits="6" timeout="30" action="${xml(action)}" method="POST"><Say>Enter the six digit code sent to your phone to continue.</Say></Gather><Hangup/>`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
method: "POST",
|
|
194
|
+
path: "/verify",
|
|
195
|
+
handler: async (ctx) => {
|
|
196
|
+
const params = new URLSearchParams(await ctx.request.text());
|
|
197
|
+
const token = await ctx.credentials.secret("twilio-auth-token");
|
|
198
|
+
if (token == null || !await signedRequest(ctx.request, token, params))
|
|
199
|
+
return new Response(null, { status: 401 });
|
|
200
|
+
const call = callSchema.extend({ Digits: z.string().regex(/^\d{6}$/) }).safeParse(Object.fromEntries(params));
|
|
201
|
+
if (!call.success) return twiml("<Say>Verification failed.</Say><Hangup/>");
|
|
202
|
+
const key = stateKey("twilio-proof", call.data.CallSid);
|
|
203
|
+
const proof = z.object({ from: z.string(), to: z.string(), hash: z.string(), attempts: z.number() }).safeParse(await ctx.store.get(key));
|
|
204
|
+
if (!proof.success || proof.data.from !== call.data.From || proof.data.to !== call.data.To || proof.data.attempts >= 5)
|
|
205
|
+
return twiml("<Say>This code has expired. Please call again.</Say><Hangup/>");
|
|
206
|
+
if (proof.data.hash !== await hash(call.data.Digits)) {
|
|
207
|
+
await ctx.store.set(
|
|
208
|
+
key,
|
|
209
|
+
{ ...proof.data, attempts: proof.data.attempts + 1 },
|
|
210
|
+
{ ttlSeconds: 300 }
|
|
211
|
+
);
|
|
212
|
+
return twiml("<Say>Verification failed. Please call again.</Say><Hangup/>");
|
|
213
|
+
}
|
|
214
|
+
const person = await ctx.people.resolve({ id: call.data.From });
|
|
215
|
+
if (person.kind !== "member")
|
|
216
|
+
return twiml(
|
|
217
|
+
"<Say>This number is no longer linked to an active account.</Say><Hangup/>"
|
|
218
|
+
);
|
|
219
|
+
await ctx.store.set(key, { ...proof.data, attempts: 5 }, { ttlSeconds: 300 });
|
|
220
|
+
return streamResponse(ctx, config, call.data, true);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
method: "WS",
|
|
225
|
+
path: "/stream",
|
|
226
|
+
handler: async (ctx) => {
|
|
227
|
+
const token = await ctx.credentials.secret("twilio-auth-token");
|
|
228
|
+
if (token == null) return new Response(null, { status: 401 });
|
|
229
|
+
const url = new URL(ctx.request.url);
|
|
230
|
+
const socketUrl = new URL(url);
|
|
231
|
+
socketUrl.protocol = url.protocol === "http:" ? "ws:" : url.protocol === "https:" ? "wss:" : url.protocol;
|
|
232
|
+
const candidates = [
|
|
233
|
+
.../* @__PURE__ */ new Set([
|
|
234
|
+
url.href,
|
|
235
|
+
socketUrl.href,
|
|
236
|
+
url.href.replace(/\/?$/, "/"),
|
|
237
|
+
socketUrl.href.replace(/\/?$/, "/")
|
|
238
|
+
])
|
|
239
|
+
];
|
|
240
|
+
let verified = false;
|
|
241
|
+
for (const candidate of candidates)
|
|
242
|
+
if (await signedRequest(
|
|
243
|
+
{ url: candidate, headers: ctx.request.headers },
|
|
244
|
+
token,
|
|
245
|
+
new URLSearchParams()
|
|
246
|
+
)) {
|
|
247
|
+
verified = true;
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
if (!verified) return new Response(null, { status: 401 });
|
|
251
|
+
ctx.socket.accept();
|
|
252
|
+
const iterator = ctx.socket.messages[Symbol.asyncIterator]();
|
|
253
|
+
let started = null;
|
|
254
|
+
while (started == null) {
|
|
255
|
+
const next = await iterator.next();
|
|
256
|
+
if (next.done) return;
|
|
257
|
+
const event = z.object({
|
|
258
|
+
event: z.string(),
|
|
259
|
+
start: z.object({
|
|
260
|
+
callSid: z.string(),
|
|
261
|
+
streamSid: z.string(),
|
|
262
|
+
customParameters: z.object({ ticket: z.string() })
|
|
263
|
+
}).optional()
|
|
264
|
+
}).safeParse(typeof next.value === "string" ? parseJson(next.value) : null);
|
|
265
|
+
if (event.success && event.data.event === "start" && event.data.start != null)
|
|
266
|
+
started = event.data.start;
|
|
267
|
+
}
|
|
268
|
+
const savedKey = stateKey("twilio-call", started.customParameters.ticket);
|
|
269
|
+
const saved = z.object({ id: z.string(), from: z.string(), to: z.string() }).safeParse(await ctx.store.get(savedKey));
|
|
270
|
+
if (!saved.success || saved.data.id !== started.callSid) {
|
|
271
|
+
ctx.socket.close(1008, "Unknown call");
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
await ctx.store.set(savedKey, null);
|
|
275
|
+
const messages = createQueue();
|
|
276
|
+
const streamSid = started.streamSid;
|
|
277
|
+
const finished = new AbortController();
|
|
278
|
+
const signal = AbortSignal.any([ctx.socket.signal, finished.signal]);
|
|
279
|
+
const incoming = { [Symbol.asyncIterator]: () => iterator };
|
|
280
|
+
let sentAudio = false;
|
|
281
|
+
const markName = crypto.randomUUID();
|
|
282
|
+
const playbackDrained = Promise.withResolvers();
|
|
283
|
+
const closing = { request: null };
|
|
284
|
+
const input = (async () => {
|
|
285
|
+
try {
|
|
286
|
+
for await (const value of abortable(incoming, signal)) {
|
|
287
|
+
const event = z.object({
|
|
288
|
+
event: z.string(),
|
|
289
|
+
media: z.object({ payload: z.string() }).optional(),
|
|
290
|
+
mark: z.object({ name: z.string() }).optional()
|
|
291
|
+
}).safeParse(typeof value === "string" ? parseJson(value) : null);
|
|
292
|
+
if (!event.success) continue;
|
|
293
|
+
if (event.data.event === "mark" && event.data.mark?.name === markName)
|
|
294
|
+
playbackDrained.resolve();
|
|
295
|
+
if (event.data.event === "media" && event.data.media != null)
|
|
296
|
+
messages.push(
|
|
297
|
+
Uint8Array.from(atob(event.data.media.payload), (char) => char.charCodeAt(0))
|
|
298
|
+
);
|
|
299
|
+
if (event.data.event === "stop") {
|
|
300
|
+
messages.push(JSON.stringify({ type: "stop" }));
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} finally {
|
|
305
|
+
playbackDrained.resolve();
|
|
306
|
+
messages.close();
|
|
307
|
+
}
|
|
308
|
+
})().catch(() => {
|
|
309
|
+
ctx.socket.close(1011, "Phone input failed");
|
|
310
|
+
finished.abort();
|
|
311
|
+
});
|
|
312
|
+
const socket = {
|
|
313
|
+
signal,
|
|
314
|
+
messages: messages.stream,
|
|
315
|
+
accept: () => void 0,
|
|
316
|
+
close: (code, reason) => {
|
|
317
|
+
if (!sentAudio) {
|
|
318
|
+
ctx.socket.close(code, reason);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (closing.request != null) return;
|
|
322
|
+
closing.request = { code, reason };
|
|
323
|
+
ctx.socket.send(
|
|
324
|
+
JSON.stringify({ event: "mark", streamSid, mark: { name: markName } })
|
|
325
|
+
);
|
|
326
|
+
},
|
|
327
|
+
send: (data) => {
|
|
328
|
+
if (data instanceof Uint8Array) {
|
|
329
|
+
sentAudio = true;
|
|
330
|
+
ctx.socket.send(
|
|
331
|
+
JSON.stringify({ event: "media", streamSid, media: { payload: toBase64(data) } })
|
|
332
|
+
);
|
|
333
|
+
} else {
|
|
334
|
+
const control = z.object({ type: z.string() }).safeParse(parseJson(data));
|
|
335
|
+
if (control.success && control.data.type === "speech.start")
|
|
336
|
+
ctx.socket.send(JSON.stringify({ event: "clear", streamSid }));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
try {
|
|
341
|
+
await runVoiceConnection({
|
|
342
|
+
ctx,
|
|
343
|
+
socket,
|
|
344
|
+
principal: { id: saved.data.from },
|
|
345
|
+
place: {
|
|
346
|
+
kind: "call",
|
|
347
|
+
id: saved.data.id,
|
|
348
|
+
parent: { kind: "number", id: saved.data.to }
|
|
349
|
+
},
|
|
350
|
+
encoding: "mulaw",
|
|
351
|
+
sampleRate: 8e3,
|
|
352
|
+
voice: config.voice,
|
|
353
|
+
recording: true
|
|
354
|
+
});
|
|
355
|
+
const requested = closing.request;
|
|
356
|
+
if (requested != null && !signal.aborted) {
|
|
357
|
+
const timer = setTimeout(() => playbackDrained.resolve(), 1e4);
|
|
358
|
+
const abortDrain = () => playbackDrained.resolve();
|
|
359
|
+
signal.addEventListener("abort", abortDrain, { once: true });
|
|
360
|
+
if (signal.aborted) abortDrain();
|
|
361
|
+
try {
|
|
362
|
+
await playbackDrained.promise;
|
|
363
|
+
} finally {
|
|
364
|
+
clearTimeout(timer);
|
|
365
|
+
signal.removeEventListener("abort", abortDrain);
|
|
366
|
+
}
|
|
367
|
+
ctx.socket.close(requested.code, requested.reason);
|
|
368
|
+
}
|
|
369
|
+
} finally {
|
|
370
|
+
finished.abort();
|
|
371
|
+
messages.close();
|
|
372
|
+
await input;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
]
|
|
377
|
+
});
|
|
378
|
+
};
|
|
379
|
+
export {
|
|
380
|
+
phoneChannel
|
|
381
|
+
};
|
|
382
|
+
//# sourceMappingURL=phone.mjs.map
|