@codori/client 0.0.6 → 0.0.7

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.
@@ -8,6 +8,7 @@ import {
8
8
  removePendingUserMessageId,
9
9
  resolvePromptSubmitStatus,
10
10
  resolveTurnSubmissionMethod,
11
+ shouldApplyNotificationToCurrentTurn,
11
12
  shouldSubmitViaTurnSteer,
12
13
  shouldAwaitThreadHydration,
13
14
  shouldRetrySteerWithTurnStart,
@@ -135,6 +136,7 @@ const composerError = computed(() => attachmentError.value ?? error.value)
135
136
  const submitError = computed(() => composerError.value ? new Error(composerError.value) : undefined)
136
137
  const interruptRequested = ref(false)
137
138
  const awaitingAssistantOutput = ref(false)
139
+ const sendMessageLocked = ref(false)
138
140
  const isBusy = computed(() =>
139
141
  status.value === 'submitted'
140
142
  || status.value === 'streaming'
@@ -467,7 +469,11 @@ const ensurePendingLiveStream = async () => {
467
469
  }
468
470
 
469
471
  const turnId = notificationTurnId(notification)
470
- if (turnId && turnId !== liveStream.turnId) {
472
+ if (!shouldApplyNotificationToCurrentTurn({
473
+ liveStreamTurnId: liveStream.turnId,
474
+ notificationMethod: notification.method,
475
+ notificationTurnId: turnId
476
+ })) {
471
477
  return
472
478
  }
473
479
 
@@ -806,7 +812,11 @@ const hydrateThread = async (threadId: string) => {
806
812
  }
807
813
 
808
814
  const turnId = notificationTurnId(notification)
809
- if (turnId && turnId !== nextLiveStream.turnId) {
815
+ if (!shouldApplyNotificationToCurrentTurn({
816
+ liveStreamTurnId: nextLiveStream.turnId,
817
+ notificationMethod: notification.method,
818
+ notificationTurnId: turnId
819
+ })) {
810
820
  return
811
821
  }
812
822
 
@@ -1764,118 +1774,131 @@ const applyNotification = (notification: CodexRpcNotification) => {
1764
1774
  }
1765
1775
 
1766
1776
  const sendMessage = async () => {
1777
+ if (sendMessageLocked.value) {
1778
+ return
1779
+ }
1780
+
1781
+ sendMessageLocked.value = true
1767
1782
  const text = input.value.trim()
1768
1783
  const submittedAttachments = attachments.value.slice()
1769
1784
 
1770
1785
  if (!text && submittedAttachments.length === 0) {
1786
+ sendMessageLocked.value = false
1771
1787
  return
1772
1788
  }
1773
1789
 
1790
+ input.value = ''
1791
+ clearAttachments({ revoke: false })
1792
+
1774
1793
  try {
1775
1794
  await loadPromptControls()
1776
1795
  } catch (caughtError) {
1796
+ restoreDraftIfPristine(text, submittedAttachments)
1777
1797
  error.value = caughtError instanceof Error ? caughtError.message : String(caughtError)
1778
1798
  status.value = 'error'
1799
+ sendMessageLocked.value = false
1779
1800
  return
1780
1801
  }
1781
1802
 
1782
- if (pendingThreadHydration && shouldAwaitThreadHydration({
1783
- hasPendingThreadHydration: true,
1784
- routeThreadId: routeThreadId.value
1785
- })) {
1786
- await pendingThreadHydration
1787
- }
1803
+ try {
1804
+ if (pendingThreadHydration && shouldAwaitThreadHydration({
1805
+ hasPendingThreadHydration: true,
1806
+ routeThreadId: routeThreadId.value
1807
+ })) {
1808
+ await pendingThreadHydration
1809
+ }
1788
1810
 
1789
- pinnedToBottom.value = true
1790
- error.value = null
1791
- attachmentError.value = null
1792
- const submissionMethod = resolveTurnSubmissionMethod(shouldSubmitWithTurnSteer())
1793
- if (submissionMethod === 'turn/start') {
1794
- status.value = 'submitted'
1795
- }
1796
- input.value = ''
1797
- clearAttachments({ revoke: false })
1798
- const optimisticMessage = buildOptimisticMessage(text, submittedAttachments)
1799
- const optimisticMessageId = optimisticMessage.id
1800
- rememberOptimisticAttachments(optimisticMessageId, submittedAttachments)
1801
- messages.value = [...messages.value, optimisticMessage]
1802
- markAwaitingAssistantOutput(shouldAwaitAssistantOutput(submissionMethod))
1803
- let startedLiveStream: LiveStream | null = null
1804
- let executedSubmissionMethod = submissionMethod
1811
+ pinnedToBottom.value = true
1812
+ error.value = null
1813
+ attachmentError.value = null
1814
+ const submissionMethod = resolveTurnSubmissionMethod(shouldSubmitWithTurnSteer())
1815
+ if (submissionMethod === 'turn/start') {
1816
+ status.value = 'submitted'
1817
+ }
1818
+ const optimisticMessage = buildOptimisticMessage(text, submittedAttachments)
1819
+ const optimisticMessageId = optimisticMessage.id
1820
+ rememberOptimisticAttachments(optimisticMessageId, submittedAttachments)
1821
+ messages.value = [...messages.value, optimisticMessage]
1822
+ markAwaitingAssistantOutput(shouldAwaitAssistantOutput(submissionMethod))
1823
+ let startedLiveStream: LiveStream | null = null
1824
+ let executedSubmissionMethod = submissionMethod
1805
1825
 
1806
- try {
1807
- const client = getClient(props.projectId)
1826
+ try {
1827
+ const client = getClient(props.projectId)
1808
1828
 
1809
- if (submissionMethod === 'turn/steer') {
1810
- const liveStream = await ensurePendingLiveStream()
1811
- queuePendingUserMessage(liveStream, optimisticMessageId)
1812
- let uploadedAttachments: PersistedProjectAttachment[] | undefined
1813
-
1814
- try {
1815
- uploadedAttachments = await uploadAttachments(liveStream.threadId, submittedAttachments)
1816
- const turnId = await waitForLiveStreamTurnId(liveStream)
1817
-
1818
- await client.request<TurnStartResponse>('turn/steer', {
1819
- threadId: liveStream.threadId,
1820
- expectedTurnId: turnId,
1821
- input: buildTurnStartInput(text, uploadedAttachments),
1822
- ...buildTurnOverrides(selectedModel.value, selectedEffort.value)
1823
- })
1824
- tokenUsage.value = null
1825
- } catch (caughtError) {
1826
- const errorToHandle = caughtError instanceof Error ? caughtError : new Error(String(caughtError))
1827
- if (!shouldRetrySteerWithTurnStart(errorToHandle.message)) {
1828
- throw errorToHandle
1829
- }
1829
+ if (submissionMethod === 'turn/steer') {
1830
+ const liveStream = await ensurePendingLiveStream()
1831
+ queuePendingUserMessage(liveStream, optimisticMessageId)
1832
+ let uploadedAttachments: PersistedProjectAttachment[] | undefined
1830
1833
 
1831
- executedSubmissionMethod = 'turn/start'
1832
- startedLiveStream = liveStream
1833
- status.value = 'submitted'
1834
- setLiveStreamTurnId(liveStream, null)
1835
- setLiveStreamInterruptRequested(liveStream, false)
1834
+ try {
1835
+ uploadedAttachments = await uploadAttachments(liveStream.threadId, submittedAttachments)
1836
+ const turnId = await waitForLiveStreamTurnId(liveStream)
1836
1837
 
1837
- await submitTurnStart({
1838
- client,
1839
- liveStream,
1840
- text,
1841
- submittedAttachments,
1842
- uploadedAttachments,
1843
- optimisticMessageId,
1844
- queueOptimisticMessage: false
1845
- })
1838
+ await client.request<TurnStartResponse>('turn/steer', {
1839
+ threadId: liveStream.threadId,
1840
+ expectedTurnId: turnId,
1841
+ input: buildTurnStartInput(text, uploadedAttachments),
1842
+ ...buildTurnOverrides(selectedModel.value, selectedEffort.value)
1843
+ })
1844
+ tokenUsage.value = null
1845
+ } catch (caughtError) {
1846
+ const errorToHandle = caughtError instanceof Error ? caughtError : new Error(String(caughtError))
1847
+ if (!shouldRetrySteerWithTurnStart(errorToHandle.message)) {
1848
+ throw errorToHandle
1849
+ }
1850
+
1851
+ executedSubmissionMethod = 'turn/start'
1852
+ startedLiveStream = liveStream
1853
+ status.value = 'submitted'
1854
+ setLiveStreamTurnId(liveStream, null)
1855
+ setLiveStreamInterruptRequested(liveStream, false)
1856
+
1857
+ await submitTurnStart({
1858
+ client,
1859
+ liveStream,
1860
+ text,
1861
+ submittedAttachments,
1862
+ uploadedAttachments,
1863
+ optimisticMessageId,
1864
+ queueOptimisticMessage: false
1865
+ })
1866
+ }
1867
+ return
1846
1868
  }
1847
- return
1848
- }
1849
1869
 
1850
- const liveStream = await ensurePendingLiveStream()
1851
- startedLiveStream = liveStream
1852
- await submitTurnStart({
1853
- client,
1854
- liveStream,
1855
- text,
1856
- submittedAttachments,
1857
- optimisticMessageId
1858
- })
1859
- } catch (caughtError) {
1860
- const messageText = caughtError instanceof Error ? caughtError.message : String(caughtError)
1870
+ const liveStream = await ensurePendingLiveStream()
1871
+ startedLiveStream = liveStream
1872
+ await submitTurnStart({
1873
+ client,
1874
+ liveStream,
1875
+ text,
1876
+ submittedAttachments,
1877
+ optimisticMessageId
1878
+ })
1879
+ } catch (caughtError) {
1880
+ const messageText = caughtError instanceof Error ? caughtError.message : String(caughtError)
1861
1881
 
1862
- markAwaitingAssistantOutput(false)
1863
- untrackPendingUserMessage(optimisticMessageId)
1864
- removeOptimisticMessage(optimisticMessageId)
1882
+ markAwaitingAssistantOutput(false)
1883
+ untrackPendingUserMessage(optimisticMessageId)
1884
+ removeOptimisticMessage(optimisticMessageId)
1865
1885
 
1866
- if (executedSubmissionMethod === 'turn/start') {
1867
- if (startedLiveStream && session.liveStream === startedLiveStream) {
1868
- clearPendingOptimisticMessages(clearLiveStream(new Error(messageText)))
1886
+ if (executedSubmissionMethod === 'turn/start') {
1887
+ if (startedLiveStream && session.liveStream === startedLiveStream) {
1888
+ clearPendingOptimisticMessages(clearLiveStream(new Error(messageText)))
1889
+ }
1890
+ session.pendingLiveStream = null
1891
+ restoreDraftIfPristine(text, submittedAttachments)
1892
+ error.value = messageText
1893
+ status.value = 'error'
1894
+ return
1869
1895
  }
1870
- session.pendingLiveStream = null
1896
+
1871
1897
  restoreDraftIfPristine(text, submittedAttachments)
1872
1898
  error.value = messageText
1873
- status.value = 'error'
1874
- return
1875
1899
  }
1876
-
1877
- restoreDraftIfPristine(text, submittedAttachments)
1878
- error.value = messageText
1900
+ } finally {
1901
+ sendMessageLocked.value = false
1879
1902
  }
1880
1903
  }
1881
1904
 
@@ -2077,19 +2100,11 @@ watch([selectedModel, availableModels], () => {
2077
2100
  />
2078
2101
  </template>
2079
2102
  <template #indicator>
2080
- <div
2103
+ <UChatShimmer
2081
2104
  v-if="awaitingAssistantOutput"
2082
- class="flex items-center gap-3 px-1 py-2 text-sm text-muted"
2083
- >
2084
- <UIcon
2085
- name="i-lucide-loader-circle"
2086
- class="size-4 animate-spin text-primary"
2087
- />
2088
- <div class="flex flex-col gap-1">
2089
- <UChatShimmer text="Waiting for response…" />
2090
- <span class="text-xs text-toned">Real reasoning will appear separately when streaming starts.</span>
2091
- </div>
2092
- </div>
2105
+ text="Thinking..."
2106
+ class="px-1 py-2"
2107
+ />
2093
2108
  </template>
2094
2109
  </UChatMessages>
2095
2110
  </div>
@@ -31,9 +31,10 @@ export const shouldSubmitViaTurnSteer = (input: {
31
31
  liveStreamTurnId: string | null
32
32
  status: PromptSubmitStatus
33
33
  }) =>
34
- input.activeThreadId !== null
35
- && input.liveStreamThreadId === input.activeThreadId
36
- && (input.liveStreamTurnId !== null || input.status === 'submitted' || input.status === 'streaming')
34
+ (input.activeThreadId !== null
35
+ && input.liveStreamThreadId === input.activeThreadId
36
+ && input.status === 'submitted')
37
+ || hasSteerableTurn(input)
37
38
 
38
39
  export const shouldAwaitThreadHydration = (input: {
39
40
  hasPendingThreadHydration: boolean
@@ -43,7 +44,17 @@ export const shouldAwaitThreadHydration = (input: {
43
44
  && input.routeThreadId !== null
44
45
 
45
46
  export const shouldRetrySteerWithTurnStart = (message: string) =>
46
- /no active turn to steer/i.test(message)
47
+ /no active turn to steer|active turn is no longer available/i.test(message)
48
+
49
+ export const shouldApplyNotificationToCurrentTurn = (input: {
50
+ liveStreamTurnId: string | null
51
+ notificationMethod: string
52
+ notificationTurnId: string | null
53
+ }) =>
54
+ input.liveStreamTurnId === null
55
+ || input.notificationTurnId === null
56
+ || input.notificationTurnId === input.liveStreamTurnId
57
+ || input.notificationMethod === 'turn/started'
47
58
 
48
59
  export const resolvePromptSubmitStatus = (input: {
49
60
  status: PromptSubmitStatus
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codori/client",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "private": false,
5
5
  "description": "Codori Nuxt dashboard for project browsing, Codex chat, and thread resume.",
6
6
  "type": "module",